Deployed e3be2c0 to 5.4 with MkDocs 1.1.2 and mike 0.5.5
authorWoltLab GmbH <woltlab@woltlab.com>
Tue, 6 Apr 2021 10:29:03 +0000 (10:29 +0000)
committerWoltLab GmbH <woltlab@woltlab.com>
Tue, 6 Apr 2021 10:29:03 +0000 (10:29 +0000)
246 files changed:
5.4/404.html
5.4/assets/javascripts/bundle.5cf3e710.min.js [deleted file]
5.4/assets/javascripts/bundle.5cf3e710.min.js.map [deleted file]
5.4/assets/javascripts/bundle.d892486b.min.js [new file with mode: 0644]
5.4/assets/javascripts/bundle.d892486b.min.js.map [new file with mode: 0644]
5.4/assets/javascripts/workers/search.fb4a9340.min.js [deleted file]
5.4/assets/javascripts/workers/search.fb4a9340.min.js.map [deleted file]
5.4/assets/javascripts/workers/search.fe42c31b.min.js [new file with mode: 0644]
5.4/assets/javascripts/workers/search.fe42c31b.min.js.map [new file with mode: 0644]
5.4/assets/stylesheets/main.33e2939f.min.css [new file with mode: 0644]
5.4/assets/stylesheets/main.33e2939f.min.css.map [new file with mode: 0644]
5.4/assets/stylesheets/main.77f3fd56.min.css [deleted file]
5.4/assets/stylesheets/main.77f3fd56.min.css.map [deleted file]
5.4/assets/stylesheets/palette.7fa14f5b.min.css [deleted file]
5.4/assets/stylesheets/palette.7fa14f5b.min.css.map [deleted file]
5.4/assets/stylesheets/palette.ef6f36e2.min.css [new file with mode: 0644]
5.4/assets/stylesheets/palette.ef6f36e2.min.css.map [new file with mode: 0644]
5.4/getting-started/index.html
5.4/index.html
5.4/javascript/code-snippets/index.html
5.4/javascript/general-usage/index.html
5.4/javascript/helper-functions/index.html
5.4/javascript/legacy-api/index.html
5.4/javascript/new-api_ajax/index.html
5.4/javascript/new-api_browser/index.html
5.4/javascript/new-api_core/index.html
5.4/javascript/new-api_data-structures/index.html
5.4/javascript/new-api_dialogs/index.html
5.4/javascript/new-api_dom/index.html
5.4/javascript/new-api_events/index.html
5.4/javascript/new-api_ui/index.html
5.4/javascript/new-api_writing-a-module/index.html
5.4/migration/wcf21/css/index.html
5.4/migration/wcf21/package/index.html
5.4/migration/wcf21/php/index.html
5.4/migration/wcf21/templates/index.html
5.4/migration/wsc30/css/index.html
5.4/migration/wsc30/javascript/index.html
5.4/migration/wsc30/package/index.html
5.4/migration/wsc30/php/index.html
5.4/migration/wsc30/templates/index.html
5.4/migration/wsc31/form-builder/index.html
5.4/migration/wsc31/like/index.html
5.4/migration/wsc31/php/index.html
5.4/migration/wsc52/libraries/index.html
5.4/migration/wsc52/php/index.html
5.4/migration/wsc52/templates/index.html
5.4/migration/wsc53/javascript/index.html
5.4/migration/wsc53/libraries/index.html
5.4/migration/wsc53/php/index.html
5.4/migration/wsc53/session/index.html
5.4/migration/wsc53/templates/index.html
5.4/package/database-php-api/index.html
5.4/package/package-xml/index.html
5.4/package/pip/acl-option/index.html
5.4/package/pip/acp-menu/index.html
5.4/package/pip/acp-search-provider/index.html
5.4/package/pip/acp-template/index.html
5.4/package/pip/bbcode/index.html
5.4/package/pip/box/index.html
5.4/package/pip/clipboard-action/index.html
5.4/package/pip/core-object/index.html
5.4/package/pip/cronjob/index.html
5.4/package/pip/database/index.html
5.4/package/pip/event-listener/index.html
5.4/package/pip/file/index.html
5.4/package/pip/index.html
5.4/package/pip/language/index.html
5.4/package/pip/media-provider/index.html
5.4/package/pip/menu-item/index.html
5.4/package/pip/menu/index.html
5.4/package/pip/object-type-definition/index.html
5.4/package/pip/object-type/index.html
5.4/package/pip/option/index.html
5.4/package/pip/page/index.html
5.4/package/pip/pip/index.html
5.4/package/pip/script/index.html
5.4/package/pip/smiley/index.html
5.4/package/pip/sql/index.html
5.4/package/pip/style/index.html
5.4/package/pip/template-listener/index.html
5.4/package/pip/template/index.html
5.4/package/pip/user-group-option/index.html
5.4/package/pip/user-menu/index.html
5.4/package/pip/user-notification-event/index.html
5.4/package/pip/user-option/index.html
5.4/package/pip/user-profile-menu/index.html
5.4/php/api/caches/index.html
5.4/php/api/caches_persistent-caches/index.html
5.4/php/api/caches_runtime-caches/index.html
5.4/php/api/comments/index.html
5.4/php/api/cronjobs/index.html
5.4/php/api/event_list/index.html
5.4/php/api/events/index.html
5.4/php/api/form_builder/dependencies/index.html
5.4/php/api/form_builder/form_fields/index.html
5.4/php/api/form_builder/overview/index.html
5.4/php/api/form_builder/structure/index.html
5.4/php/api/form_builder/validation_data/index.html
5.4/php/api/package_installation_plugins/index.html
5.4/php/api/sitemaps/index.html
5.4/php/api/user_activity_points/index.html
5.4/php/api/user_notifications/index.html
5.4/php/apps/index.html
5.4/php/code-style-documentation/index.html
5.4/php/code-style/index.html
5.4/php/database-access/index.html
5.4/php/database-objects/index.html
5.4/php/exceptions/index.html
5.4/php/gdpr/index.html
5.4/php/pages/index.html
5.4/search/search_index.json
5.4/sitemap.xml
5.4/sitemap.xml.gz
5.4/tutorial/series/overview/index.html
5.4/tutorial/series/part_1/index.html
5.4/tutorial/series/part_2/index.html
5.4/tutorial/series/part_3/index.html
5.4/view/css/index.html
5.4/view/languages-naming-conventions/index.html
5.4/view/languages/index.html
5.4/view/template-plugins/index.html
5.4/view/templates/index.html
latest/404.html
latest/assets/javascripts/bundle.5cf3e710.min.js [deleted file]
latest/assets/javascripts/bundle.5cf3e710.min.js.map [deleted file]
latest/assets/javascripts/bundle.d892486b.min.js [new file with mode: 0644]
latest/assets/javascripts/bundle.d892486b.min.js.map [new file with mode: 0644]
latest/assets/javascripts/workers/search.fb4a9340.min.js [deleted file]
latest/assets/javascripts/workers/search.fb4a9340.min.js.map [deleted file]
latest/assets/javascripts/workers/search.fe42c31b.min.js [new file with mode: 0644]
latest/assets/javascripts/workers/search.fe42c31b.min.js.map [new file with mode: 0644]
latest/assets/stylesheets/main.33e2939f.min.css [new file with mode: 0644]
latest/assets/stylesheets/main.33e2939f.min.css.map [new file with mode: 0644]
latest/assets/stylesheets/main.77f3fd56.min.css [deleted file]
latest/assets/stylesheets/main.77f3fd56.min.css.map [deleted file]
latest/assets/stylesheets/palette.7fa14f5b.min.css [deleted file]
latest/assets/stylesheets/palette.7fa14f5b.min.css.map [deleted file]
latest/assets/stylesheets/palette.ef6f36e2.min.css [new file with mode: 0644]
latest/assets/stylesheets/palette.ef6f36e2.min.css.map [new file with mode: 0644]
latest/getting-started/index.html
latest/index.html
latest/javascript/code-snippets/index.html
latest/javascript/general-usage/index.html
latest/javascript/helper-functions/index.html
latest/javascript/legacy-api/index.html
latest/javascript/new-api_ajax/index.html
latest/javascript/new-api_browser/index.html
latest/javascript/new-api_core/index.html
latest/javascript/new-api_data-structures/index.html
latest/javascript/new-api_dialogs/index.html
latest/javascript/new-api_dom/index.html
latest/javascript/new-api_events/index.html
latest/javascript/new-api_ui/index.html
latest/javascript/new-api_writing-a-module/index.html
latest/migration/wcf21/css/index.html
latest/migration/wcf21/package/index.html
latest/migration/wcf21/php/index.html
latest/migration/wcf21/templates/index.html
latest/migration/wsc30/css/index.html
latest/migration/wsc30/javascript/index.html
latest/migration/wsc30/package/index.html
latest/migration/wsc30/php/index.html
latest/migration/wsc30/templates/index.html
latest/migration/wsc31/form-builder/index.html
latest/migration/wsc31/like/index.html
latest/migration/wsc31/php/index.html
latest/migration/wsc52/libraries/index.html
latest/migration/wsc52/php/index.html
latest/migration/wsc52/templates/index.html
latest/migration/wsc53/javascript/index.html
latest/migration/wsc53/libraries/index.html
latest/migration/wsc53/php/index.html
latest/migration/wsc53/session/index.html
latest/migration/wsc53/templates/index.html
latest/package/database-php-api/index.html
latest/package/package-xml/index.html
latest/package/pip/acl-option/index.html
latest/package/pip/acp-menu/index.html
latest/package/pip/acp-search-provider/index.html
latest/package/pip/acp-template/index.html
latest/package/pip/bbcode/index.html
latest/package/pip/box/index.html
latest/package/pip/clipboard-action/index.html
latest/package/pip/core-object/index.html
latest/package/pip/cronjob/index.html
latest/package/pip/database/index.html
latest/package/pip/event-listener/index.html
latest/package/pip/file/index.html
latest/package/pip/index.html
latest/package/pip/language/index.html
latest/package/pip/media-provider/index.html
latest/package/pip/menu-item/index.html
latest/package/pip/menu/index.html
latest/package/pip/object-type-definition/index.html
latest/package/pip/object-type/index.html
latest/package/pip/option/index.html
latest/package/pip/page/index.html
latest/package/pip/pip/index.html
latest/package/pip/script/index.html
latest/package/pip/smiley/index.html
latest/package/pip/sql/index.html
latest/package/pip/style/index.html
latest/package/pip/template-listener/index.html
latest/package/pip/template/index.html
latest/package/pip/user-group-option/index.html
latest/package/pip/user-menu/index.html
latest/package/pip/user-notification-event/index.html
latest/package/pip/user-option/index.html
latest/package/pip/user-profile-menu/index.html
latest/php/api/caches/index.html
latest/php/api/caches_persistent-caches/index.html
latest/php/api/caches_runtime-caches/index.html
latest/php/api/comments/index.html
latest/php/api/cronjobs/index.html
latest/php/api/event_list/index.html
latest/php/api/events/index.html
latest/php/api/form_builder/dependencies/index.html
latest/php/api/form_builder/form_fields/index.html
latest/php/api/form_builder/overview/index.html
latest/php/api/form_builder/structure/index.html
latest/php/api/form_builder/validation_data/index.html
latest/php/api/package_installation_plugins/index.html
latest/php/api/sitemaps/index.html
latest/php/api/user_activity_points/index.html
latest/php/api/user_notifications/index.html
latest/php/apps/index.html
latest/php/code-style-documentation/index.html
latest/php/code-style/index.html
latest/php/database-access/index.html
latest/php/database-objects/index.html
latest/php/exceptions/index.html
latest/php/gdpr/index.html
latest/php/pages/index.html
latest/search/search_index.json
latest/sitemap.xml
latest/sitemap.xml.gz
latest/tutorial/series/overview/index.html
latest/tutorial/series/part_1/index.html
latest/tutorial/series/part_2/index.html
latest/tutorial/series/part_3/index.html
latest/view/css/index.html
latest/view/languages-naming-conventions/index.html
latest/view/languages/index.html
latest/view/template-plugins/index.html
latest/view/templates/index.html

index b275a4214f9fb83ebf2eb391c943645ecceb81f1..4ac2f3b00cd39c6ae4843167e84d8fb87b8a1337 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="/assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="/assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="/assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="/assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="/assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="/assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("/",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="/." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="/." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="/assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="/." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="/." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="/assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "/", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "/assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "/", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "/assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="/assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="/assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
diff --git a/5.4/assets/javascripts/bundle.5cf3e710.min.js b/5.4/assets/javascripts/bundle.5cf3e710.min.js
deleted file mode 100644 (file)
index b728e04..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-(()=>{var qo=Object.create,Ct=Object.defineProperty,Ko=Object.getPrototypeOf,cr=Object.prototype.hasOwnProperty,Yo=Object.getOwnPropertyNames,Jo=Object.getOwnPropertyDescriptor,ur=Object.getOwnPropertySymbols,Xo=Object.prototype.propertyIsEnumerable;var N=Object.assign,Bo=e=>Ct(e,"__esModule",{value:!0});var lr=(e,t)=>{var r={};for(var n in e)cr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&ur)for(var n of ur(e))t.indexOf(n)<0&&Xo.call(e,n)&&(r[n]=e[n]);return r},jt=(e,t)=>()=>(t||(t={exports:{}},e(t.exports,t)),t.exports);var Go=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Yo(t))!cr.call(e,n)&&n!=="default"&&Ct(e,n,{get:()=>t[n],enumerable:!(r=Jo(t,n))||r.enumerable});return e},it=e=>e&&e.__esModule?e:Go(Bo(Ct(e!=null?qo(Ko(e)):{},"default",{value:e,enumerable:!0})),e);var pr=jt((kt,fr)=>{(function(e,t){typeof kt=="object"&&typeof fr!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kt,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function c(S){return!!(S&&S!==document&&S.nodeName!=="HTML"&&S.nodeName!=="BODY"&&"classList"in S&&"contains"in S.classList)}function u(S){var Ye=S.type,Ht=S.tagName;return!!(Ht==="INPUT"&&a[Ye]&&!S.readOnly||Ht==="TEXTAREA"&&!S.readOnly||S.isContentEditable)}function s(S){S.classList.contains("focus-visible")||(S.classList.add("focus-visible"),S.setAttribute("data-focus-visible-added",""))}function l(S){!S.hasAttribute("data-focus-visible-added")||(S.classList.remove("focus-visible"),S.removeAttribute("data-focus-visible-added"))}function p(S){S.metaKey||S.altKey||S.ctrlKey||(c(r.activeElement)&&s(r.activeElement),n=!0)}function d(S){n=!1}function _(S){!c(S.target)||(n||u(S.target))&&s(S.target)}function $(S){!c(S.target)||(S.target.classList.contains("focus-visible")||S.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),l(S.target))}function A(S){document.visibilityState==="hidden"&&(o&&(n=!0),Z())}function Z(){document.addEventListener("mousemove",F),document.addEventListener("mousedown",F),document.addEventListener("mouseup",F),document.addEventListener("pointermove",F),document.addEventListener("pointerdown",F),document.addEventListener("pointerup",F),document.addEventListener("touchmove",F),document.addEventListener("touchstart",F),document.addEventListener("touchend",F)}function P(){document.removeEventListener("mousemove",F),document.removeEventListener("mousedown",F),document.removeEventListener("mouseup",F),document.removeEventListener("pointermove",F),document.removeEventListener("pointerdown",F),document.removeEventListener("pointerup",F),document.removeEventListener("touchmove",F),document.removeEventListener("touchstart",F),document.removeEventListener("touchend",F)}function F(S){S.target.nodeName&&S.target.nodeName.toLowerCase()==="html"||(n=!1,P())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",A,!0),Z(),r.addEventListener("focus",_,!0),r.addEventListener("blur",$,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Bt=jt((ot,Xt)=>{(function(t,r){typeof ot=="object"&&typeof Xt=="object"?Xt.exports=r():typeof define=="function"&&define.amd?define([],r):typeof ot=="object"?ot.ClipboardJS=r():t.ClipboardJS=r()})(ot,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(n,o,i){r.o(n,o)||Object.defineProperty(n,o,{enumerable:!0,get:i})},r.r=function(n){typeof Symbol!="undefined"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},r.t=function(n,o){if(o&1&&(n=r(n)),o&8||o&4&&typeof n=="object"&&n&&n.__esModule)return n;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),o&2&&typeof n!="string")for(var a in n)r.d(i,a,function(c){return n[c]}.bind(null,a));return i},r.n=function(n){var o=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(o,"a",o),o},r.o=function(n,o){return Object.prototype.hasOwnProperty.call(n,o)},r.p="",r(r.s=6)}([function(e,t){function r(n){var o;if(n.nodeName==="SELECT")n.focus(),o=n.value;else if(n.nodeName==="INPUT"||n.nodeName==="TEXTAREA"){var i=n.hasAttribute("readonly");i||n.setAttribute("readonly",""),n.select(),n.setSelectionRange(0,n.value.length),i||n.removeAttribute("readonly"),o=n.value}else{n.hasAttribute("contenteditable")&&n.focus();var a=window.getSelection(),c=document.createRange();c.selectNodeContents(n),a.removeAllRanges(),a.addRange(c),o=a.toString()}return o}e.exports=r},function(e,t){function r(){}r.prototype={on:function(n,o,i){var a=this.e||(this.e={});return(a[n]||(a[n]=[])).push({fn:o,ctx:i}),this},once:function(n,o,i){var a=this;function c(){a.off(n,c),o.apply(i,arguments)}return c._=o,this.on(n,c,i)},emit:function(n){var o=[].slice.call(arguments,1),i=((this.e||(this.e={}))[n]||[]).slice(),a=0,c=i.length;for(a;a<c;a++)i[a].fn.apply(i[a].ctx,o);return this},off:function(n,o){var i=this.e||(this.e={}),a=i[n],c=[];if(a&&o)for(var u=0,s=a.length;u<s;u++)a[u].fn!==o&&a[u].fn._!==o&&c.push(a[u]);return c.length?i[n]=c:delete i[n],this}},e.exports=r,e.exports.TinyEmitter=r},function(e,t,r){var n=r(3),o=r(4);function i(s,l,p){if(!s&&!l&&!p)throw new Error("Missing required arguments");if(!n.string(l))throw new TypeError("Second argument must be a String");if(!n.fn(p))throw new TypeError("Third argument must be a Function");if(n.node(s))return a(s,l,p);if(n.nodeList(s))return c(s,l,p);if(n.string(s))return u(s,l,p);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function a(s,l,p){return s.addEventListener(l,p),{destroy:function(){s.removeEventListener(l,p)}}}function c(s,l,p){return Array.prototype.forEach.call(s,function(d){d.addEventListener(l,p)}),{destroy:function(){Array.prototype.forEach.call(s,function(d){d.removeEventListener(l,p)})}}}function u(s,l,p){return o(document.body,s,l,p)}e.exports=i},function(e,t){t.node=function(r){return r!==void 0&&r instanceof HTMLElement&&r.nodeType===1},t.nodeList=function(r){var n=Object.prototype.toString.call(r);return r!==void 0&&(n==="[object NodeList]"||n==="[object HTMLCollection]")&&"length"in r&&(r.length===0||t.node(r[0]))},t.string=function(r){return typeof r=="string"||r instanceof String},t.fn=function(r){var n=Object.prototype.toString.call(r);return n==="[object Function]"}},function(e,t,r){var n=r(5);function o(c,u,s,l,p){var d=a.apply(this,arguments);return c.addEventListener(s,d,p),{destroy:function(){c.removeEventListener(s,d,p)}}}function i(c,u,s,l,p){return typeof c.addEventListener=="function"?o.apply(null,arguments):typeof s=="function"?o.bind(null,document).apply(null,arguments):(typeof c=="string"&&(c=document.querySelectorAll(c)),Array.prototype.map.call(c,function(d){return o(d,u,s,l,p)}))}function a(c,u,s,l){return function(p){p.delegateTarget=n(p.target,u),p.delegateTarget&&l.call(c,p)}}e.exports=i},function(e,t){var r=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}function o(i,a){for(;i&&i.nodeType!==r;){if(typeof i.matches=="function"&&i.matches(a))return i;i=i.parentNode}}e.exports=o},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),i=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(E){return typeof E}:function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},a=function(){function E(h,v){for(var y=0;y<v.length;y++){var L=v[y];L.enumerable=L.enumerable||!1,L.configurable=!0,"value"in L&&(L.writable=!0),Object.defineProperty(h,L.key,L)}}return function(h,v,y){return v&&E(h.prototype,v),y&&E(h,y),h}}();function c(E,h){if(!(E instanceof h))throw new TypeError("Cannot call a class as a function")}var u=function(){function E(h){c(this,E),this.resolveOptions(h),this.initSelection()}return a(E,[{key:"resolveOptions",value:function(){var v=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=v.action,this.container=v.container,this.emitter=v.emitter,this.target=v.target,this.text=v.text,this.trigger=v.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var v=this,y=document.documentElement.getAttribute("dir")=="rtl";this.removeFake(),this.fakeHandlerCallback=function(){return v.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[y?"right":"left"]="-9999px";var L=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=L+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=o()(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=o()(this.target),this.copyText()}},{key:"copyText",value:function(){var v=void 0;try{v=document.execCommand(this.action)}catch(y){v=!1}this.handleResult(v)}},{key:"handleResult",value:function(v){this.emitter.emit(v?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var v=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"copy";if(this._action=v,this._action!=="copy"&&this._action!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(v){if(v!==void 0)if(v&&(typeof v=="undefined"?"undefined":i(v))==="object"&&v.nodeType===1){if(this.action==="copy"&&v.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(this.action==="cut"&&(v.hasAttribute("readonly")||v.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`);this._target=v}else throw new Error('Invalid "target" value, use a valid Element')},get:function(){return this._target}}]),E}(),s=u,l=r(1),p=r.n(l),d=r(2),_=r.n(d),$=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(E){return typeof E}:function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},A=function(){function E(h,v){for(var y=0;y<v.length;y++){var L=v[y];L.enumerable=L.enumerable||!1,L.configurable=!0,"value"in L&&(L.writable=!0),Object.defineProperty(h,L.key,L)}}return function(h,v,y){return v&&E(h.prototype,v),y&&E(h,y),h}}();function Z(E,h){if(!(E instanceof h))throw new TypeError("Cannot call a class as a function")}function P(E,h){if(!E)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return h&&(typeof h=="object"||typeof h=="function")?h:E}function F(E,h){if(typeof h!="function"&&h!==null)throw new TypeError("Super expression must either be null or a function, not "+typeof h);E.prototype=Object.create(h&&h.prototype,{constructor:{value:E,enumerable:!1,writable:!0,configurable:!0}}),h&&(Object.setPrototypeOf?Object.setPrototypeOf(E,h):E.__proto__=h)}var S=function(E){F(h,E);function h(v,y){Z(this,h);var L=P(this,(h.__proto__||Object.getPrototypeOf(h)).call(this));return L.resolveOptions(y),L.listenClick(v),L}return A(h,[{key:"resolveOptions",value:function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof y.action=="function"?y.action:this.defaultAction,this.target=typeof y.target=="function"?y.target:this.defaultTarget,this.text=typeof y.text=="function"?y.text:this.defaultText,this.container=$(y.container)==="object"?y.container:document.body}},{key:"listenClick",value:function(y){var L=this;this.listener=_()(y,"click",function(Je){return L.onClick(Je)})}},{key:"onClick",value:function(y){var L=y.delegateTarget||y.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s({action:this.action(L),target:this.target(L),text:this.text(L),container:this.container,trigger:L,emitter:this})}},{key:"defaultAction",value:function(y){return Ye("action",y)}},{key:"defaultTarget",value:function(y){var L=Ye("target",y);if(L)return document.querySelector(L)}},{key:"defaultText",value:function(y){return Ye("text",y)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],L=typeof y=="string"?[y]:y,Je=!!document.queryCommandSupported;return L.forEach(function(Qo){Je=Je&&!!document.queryCommandSupported(Qo)}),Je}}]),h}(p.a);function Ye(E,h){var v="data-clipboard-"+E;if(!!h.hasAttribute(v))return h.getAttribute(v)}var Ht=t.default=S}]).default})});var wo=jt((Yb,So)=>{"use strict";var Di=/["'&<>]/;So.exports=Ui;function Ui(e){var t=""+e,r=Di.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i<t.length;i++){switch(t.charCodeAt(i)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}a!==i&&(o+=t.substring(a,i)),a=i+1,o+=n}return a!==i?o+t.substring(a,i):o}});var rx=it(pr());var Ft=function(e,t){return Ft=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,n){r.__proto__=n}||function(r,n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(r[o]=n[o])},Ft(e,t)};function z(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");Ft(e,t);function r(){this.constructor=e}e.prototype=t===null?Object.create(t):(r.prototype=t.prototype,new r)}function mr(e,t,r,n){function o(i){return i instanceof r?i:new r(function(a){a(i)})}return new(r||(r=Promise))(function(i,a){function c(l){try{s(n.next(l))}catch(p){a(p)}}function u(l){try{s(n.throw(l))}catch(p){a(p)}}function s(l){l.done?i(l.value):o(l.value).then(c,u)}s((n=n.apply(e,t||[])).next())})}function dr(e,t){var r={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},n,o,i,a;return a={next:c(0),throw:c(1),return:c(2)},typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function c(s){return function(l){return u([s,l])}}function u(s){if(n)throw new TypeError("Generator is already executing.");for(;r;)try{if(n=1,o&&(i=s[0]&2?o.return:s[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,s[1])).done)return i;switch(o=0,i&&(s=[s[0]&2,i.value]),s[0]){case 0:case 1:i=s;break;case 4:return r.label++,{value:s[1],done:!1};case 5:r.label++,o=s[1],s=[0];continue;case 7:s=r.ops.pop(),r.trys.pop();continue;default:if(i=r.trys,!(i=i.length>0&&i[i.length-1])&&(s[0]===6||s[0]===2)){r=0;continue}if(s[0]===3&&(!i||s[1]>i[0]&&s[1]<i[3])){r.label=s[1];break}if(s[0]===6&&r.label<i[1]){r.label=i[1],i=s;break}if(i&&r.label<i[2]){r.label=i[2],r.ops.push(s);break}i[2]&&r.ops.pop(),r.trys.pop();continue}s=t.call(e,r)}catch(l){s=[6,l],o=0}finally{n=i=0}if(s[0]&5)throw s[1];return{value:s[0]?s[1]:void 0,done:!0}}}function ae(e){var t=typeof Symbol=="function"&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&typeof e.length=="number")return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function C(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(c){a={error:c}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function I(e,t){for(var r=0,n=t.length,o=e.length;r<n;r++,o++)e[o]=t[r];return e}function hr(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof ae=="function"?ae(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(c,u){a=e[i](a),o(c,u,a.done,a.value)})}}function o(i,a,c,u){Promise.resolve(u).then(function(s){i({value:s,done:c})},a)}}function g(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var st=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription:
-`+r.map(function(n,o){return o+1+") "+n.toString()}).join(`
-  `):"",this.name="UnsubscriptionError",this.errors=r}});function ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var te=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._teardowns=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(Array.isArray(a))try{for(var c=ae(a),u=c.next();!u.done;u=c.next()){var s=u.value;s.remove(this)}}catch(A){t={error:A}}finally{try{u&&!u.done&&(r=c.return)&&r.call(c)}finally{if(t)throw t.error}}else a==null||a.remove(this);var l=this.initialTeardown;if(g(l))try{l()}catch(A){i=A instanceof st?A.errors:[A]}var p=this._teardowns;if(p){this._teardowns=null;try{for(var d=ae(p),_=d.next();!_.done;_=d.next()){var $=_.value;try{br($)}catch(A){i=i!=null?i:[],A instanceof st?i=I(I([],C(i)),C(A.errors)):i.push(A)}}}catch(A){n={error:A}}finally{try{_&&!_.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new st(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)br(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._teardowns=(r=this._teardowns)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ve(r,t)},e.prototype.remove=function(t){var r=this._teardowns;r&&ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var It=te.EMPTY;function ct(e){return e instanceof te||e&&"closed"in e&&g(e.remove)&&g(e.add)&&g(e.unsubscribe)}function br(e){g(e)?e():e.unsubscribe()}var pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Re={setTimeout:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Re.delegate;return((r==null?void 0:r.setTimeout)||setTimeout).apply(void 0,I([],C(e)))},clearTimeout:function(e){var t=Re.delegate;return((t==null?void 0:t.clearTimeout)||clearTimeout)(e)},delegate:void 0};function ut(e){Re.setTimeout(function(){var t=pe.onUnhandledError;if(t)t(e);else throw e})}function Y(){}var vr=function(){return Rt("C",void 0,void 0)}();function gr(e){return Rt("E",void 0,e)}function yr(e){return Rt("N",e,void 0)}function Rt(e,t,r){return{kind:e,value:t,error:r}}var Xe=function(e){z(t,e);function t(r){var n=e.call(this)||this;return n.isStopped=!1,r?(n.destination=r,ct(r)&&r.add(n)):n.destination=Zo,n}return t.create=function(r,n,o){return new $t(r,n,o)},t.prototype.next=function(r){this.isStopped?Pt(yr(r),this):this._next(r)},t.prototype.error=function(r){this.isStopped?Pt(gr(r),this):(this.isStopped=!0,this._error(r))},t.prototype.complete=function(){this.isStopped?Pt(vr,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this))},t.prototype._next=function(r){this.destination.next(r)},t.prototype._error=function(r){this.destination.error(r),this.unsubscribe()},t.prototype._complete=function(){this.destination.complete(),this.unsubscribe()},t}(te);var $t=function(e){z(t,e);function t(r,n,o){var i=e.call(this)||this,a;if(g(r))a=r;else if(r){a=r.next,n=r.error,o=r.complete;var c;i&&pe.useDeprecatedNextContext?(c=Object.create(r),c.unsubscribe=function(){return i.unsubscribe()}):c=r,a=a==null?void 0:a.bind(c),n=n==null?void 0:n.bind(c),o=o==null?void 0:o.bind(c)}return i.destination={next:a?Vt(a,i):Y,error:Vt(n||xr,i),complete:o?Vt(o,i):Y},i}return t}(Xe);function Vt(e,t){return pe.useDeprecatedSynchronousErrorHandling?function(r){try{e(r)}catch(n){t.__syncError=n}}:e}function xr(e){if(pe.useDeprecatedSynchronousErrorHandling)throw e;ut(e)}function Pt(e,t){var r=pe.onStoppedNotification;r&&Re.setTimeout(function(){return r(e,t)})}var Zo={closed:!0,next:Y,error:xr,complete:Y};var Se=function(){return typeof Symbol=="function"&&Symbol.observable||"@@observable"}();function se(e){return e}function Sr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Dt(e)}function Dt(e){return e.length===0?se:e.length===1?e[0]:function(r){return e.reduce(function(n,o){return o(n)},r)}}var O=function(){function e(t){t&&(this._subscribe=t)}return e.prototype.lift=function(t){var r=new e;return r.source=this,r.operator=t,r},e.prototype.subscribe=function(t,r,n){var o=ei(t)?t:new $t(t,r,n),i=this,a=i.operator,c=i.source;if(o.add(a?a.call(o,c):c||pe.useDeprecatedSynchronousErrorHandling?this._subscribe(o):this._trySubscribe(o)),pe.useDeprecatedSynchronousErrorHandling)for(var u=o;u;){if(u.__syncError)throw u.__syncError;u=u.destination}return o},e.prototype._trySubscribe=function(t){try{return this._subscribe(t)}catch(r){t.error(r)}},e.prototype.forEach=function(t,r){var n=this;return r=wr(r),new r(function(o,i){var a;a=n.subscribe(function(c){try{t(c)}catch(u){i(u),a==null||a.unsubscribe()}},i,o)})},e.prototype._subscribe=function(t){var r;return(r=this.source)===null||r===void 0?void 0:r.subscribe(t)},e.prototype[Se]=function(){return this},e.prototype.pipe=function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return t.length?Dt(t)(this):this},e.prototype.toPromise=function(t){var r=this;return t=wr(t),new t(function(n,o){var i;r.subscribe(function(a){return i=a},function(a){return o(a)},function(){return n(i)})})},e.create=function(t){return new e(t)},e}();function wr(e){var t;return(t=e!=null?e:pe.Promise)!==null&&t!==void 0?t:Promise}function ti(e){return e&&g(e.next)&&g(e.error)&&g(e.complete)}function ei(e){return e&&e instanceof Xe||ti(e)&&ct(e)}function ri(e){return g(e==null?void 0:e.lift)}function m(e){return function(t){if(ri(t))return t.lift(function(r){try{return e(r,this)}catch(n){this.error(n)}});throw new TypeError("Unable to lift unknown Observable type")}}var b=function(e){z(t,e);function t(r,n,o,i,a){var c=e.call(this,r)||this;return c.onFinalize=a,c._next=n?function(u){try{n(u)}catch(s){this.destination.error(s)}}:e.prototype._next,c._error=o?function(u){try{o(u)}catch(s){this.destination.error(s)}this.unsubscribe()}:e.prototype._error,c._complete=i?function(){try{i()}catch(u){this.destination.error(u)}this.unsubscribe()}:e.prototype._complete,c}return t.prototype.unsubscribe=function(){var r,n=this.closed;e.prototype.unsubscribe.call(this),!n&&((r=this.onFinalize)===null||r===void 0||r.call(this))},t}(Xe);var $e={schedule:function(e){var t=requestAnimationFrame,r=cancelAnimationFrame,n=$e.delegate;n&&(t=n.requestAnimationFrame,r=n.cancelAnimationFrame);var o=t(function(i){r=void 0,e(i)});return new te(function(){return r==null?void 0:r(o)})},requestAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.requestAnimationFrame)||requestAnimationFrame).apply(void 0,I([],C(e)))},cancelAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.cancelAnimationFrame)||cancelAnimationFrame).apply(void 0,I([],C(e)))},delegate:void 0};var Er=at(function(e){return function(){e(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"}});var T=function(e){z(t,e);function t(){var r=e.call(this)||this;return r.observers=[],r.closed=!1,r.isStopped=!1,r.hasError=!1,r.thrownError=null,r}return t.prototype.lift=function(r){var n=new Or(this,this);return n.operator=r,n},t.prototype._throwIfClosed=function(){if(this.closed)throw new Er},t.prototype.next=function(r){var n,o;if(this._throwIfClosed(),!this.isStopped){var i=this.observers.slice();try{for(var a=ae(i),c=a.next();!c.done;c=a.next()){var u=c.value;u.next(r)}}catch(s){n={error:s}}finally{try{c&&!c.done&&(o=a.return)&&o.call(a)}finally{if(n)throw n.error}}}},t.prototype.error=function(r){if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=r;for(var n=this.observers;n.length;)n.shift().error(r)}},t.prototype.complete=function(){if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;for(var r=this.observers;r.length;)r.shift().complete()}},t.prototype.unsubscribe=function(){this.isStopped=this.closed=!0,this.observers=null},t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,c=o.observers;return i||a?It:(c.push(r),new te(function(){return ve(n.observers,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new O;return r.source=this,r},t.create=function(r,n){return new Or(r,n)},t}(O);var Or=function(e){z(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:It},t}(T);var Be={now:function(){return(Be.delegate||Date).now()},delegate:void 0};var lt=function(e){z(t,e);function t(r,n,o){r===void 0&&(r=Infinity),n===void 0&&(n=Infinity),o===void 0&&(o=Be);var i=e.call(this)||this;return i.bufferSize=r,i.windowTime=n,i.timestampProvider=o,i.buffer=[],i.infiniteTimeWindow=!0,i.infiniteTimeWindow=n===Infinity,i.bufferSize=Math.max(1,r),i.windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n.buffer,a=n.infiniteTimeWindow,c=n.timestampProvider,u=n.windowTime;o||(i.push(r),!a&&i.push(c.now()+u)),this.trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this.trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o.infiniteTimeWindow,a=o.buffer,c=a.slice(),u=0;u<c.length&&!r.closed;u+=i?1:2)r.next(c[u]);return this._checkFinalizedStatuses(r),n},t.prototype.trimBuffer=function(){var r=this,n=r.bufferSize,o=r.timestampProvider,i=r.buffer,a=r.infiniteTimeWindow,c=(a?1:2)*n;if(n<Infinity&&c<i.length&&i.splice(0,i.length-c),!a){for(var u=o.now(),s=0,l=1;l<i.length&&i[l]<=u;l+=2)s=l;s&&i.splice(0,s+1)}},t}(T);var Tr=function(e){z(t,e);function t(r,n){return e.call(this)||this}return t.prototype.schedule=function(r,n){return n===void 0&&(n=0),this},t}(te);var Ge={setInterval:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ge.delegate;return((r==null?void 0:r.setInterval)||setInterval).apply(void 0,I([],C(e)))},clearInterval:function(e){var t=Ge.delegate;return((t==null?void 0:t.clearInterval)||clearInterval)(e)},delegate:void 0};var ft=function(e){z(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o.pending=!1,o}return t.prototype.schedule=function(r,n){if(n===void 0&&(n=0),this.closed)return this;this.state=r;var o=this.id,i=this.scheduler;return o!=null&&(this.id=this.recycleAsyncId(i,o,n)),this.pending=!0,this.delay=n,this.id=this.id||this.requestAsyncId(i,this.id,n),this},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),Ge.setInterval(r.flush.bind(r,this),o)},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&this.delay===o&&this.pending===!1)return n;Ge.clearInterval(n)},t.prototype.execute=function(r,n){if(this.closed)return new Error("executing a cancelled action");this.pending=!1;var o=this._execute(r,n);if(o)return o;this.pending===!1&&this.id!=null&&(this.id=this.recycleAsyncId(this.scheduler,this.id,null))},t.prototype._execute=function(r,n){var o=!1,i;try{this.work(r)}catch(a){o=!0,i=!!a&&a||new Error(a)}if(o)return this.unsubscribe(),i},t.prototype.unsubscribe=function(){if(!this.closed){var r=this,n=r.id,o=r.scheduler,i=o.actions;this.work=this.state=this.scheduler=null,this.pending=!1,ve(i,this),n!=null&&(this.id=this.recycleAsyncId(o,n,null)),this.delay=null,e.prototype.unsubscribe.call(this)}},t}(Tr);var Ut=function(){function e(t,r){r===void 0&&(r=e.now),this.schedulerActionCtor=t,this.now=r}return e.prototype.schedule=function(t,r,n){return r===void 0&&(r=0),new this.schedulerActionCtor(this,t).schedule(n,r)},e.now=Be.now,e}();var pt=function(e){z(t,e);function t(r,n){n===void 0&&(n=Ut.now);var o=e.call(this,r,n)||this;return o.actions=[],o.active=!1,o.scheduled=void 0,o}return t.prototype.flush=function(r){var n=this.actions;if(this.active){n.push(r);return}var o;this.active=!0;do if(o=r.execute(r.state,r.delay))break;while(r=n.shift());if(this.active=!1,o){for(;r=n.shift();)r.unsubscribe();throw o}},t}(Ut);var Ze=new pt(ft),Mr=Ze;var Ar=function(e){z(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r.scheduled||(r.scheduled=$e.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.length===0&&($e.cancelAnimationFrame(n),r.scheduled=void 0)},t}(ft);var Lr=function(e){z(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this.active=!0,this.scheduled=void 0;var n=this.actions,o,i=-1;r=r||n.shift();var a=n.length;do if(o=r.execute(r.state,r.delay))break;while(++i<a&&(r=n.shift()));if(this.active=!1,o){for(;++i<a&&(r=n.shift());)r.unsubscribe();throw o}},t}(pt);var J=new Lr(Ar);var me=new O(function(e){return e.complete()});function Pe(e,t){return new O(function(r){var n=0;return t.schedule(function(){n===e.length?r.complete():(r.next(e[n++]),r.closed||this.schedule())})})}var Ve=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function mt(e){return g(e==null?void 0:e.then)}function ni(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var De=ni();function _r(e,t){return new O(function(r){var n=new te;return n.add(t.schedule(function(){var o=e[Se]();n.add(o.subscribe({next:function(i){n.add(t.schedule(function(){return r.next(i)}))},error:function(i){n.add(t.schedule(function(){return r.error(i)}))},complete:function(){n.add(t.schedule(function(){return r.complete()}))}}))})),n})}function Hr(e,t){return new O(function(r){return t.schedule(function(){return e.then(function(n){r.add(t.schedule(function(){r.next(n),r.add(t.schedule(function(){return r.complete()}))}))},function(n){r.add(t.schedule(function(){return r.error(n)}))})})})}function Cr(e,t,r,n){n===void 0&&(n=0);var o=t.schedule(function(){try{r.call(this)}catch(i){e.error(i)}},n);return e.add(o),o}function jr(e,t){return new O(function(r){var n;return r.add(t.schedule(function(){n=e[De](),Cr(r,t,function(){var o=n.next(),i=o.value,a=o.done;a?r.complete():(r.next(i),this.schedule())})})),function(){return g(n==null?void 0:n.return)&&n.return()}})}function dt(e){return g(e[Se])}function ht(e){return g(e==null?void 0:e[De])}function kr(e,t){if(!e)throw new Error("Iterable cannot be null");return new O(function(r){var n=new te;return n.add(t.schedule(function(){var o=e[Symbol.asyncIterator]();n.add(t.schedule(function(){var i=this;o.next().then(function(a){a.done?r.complete():(r.next(a.value),i.schedule())})}))})),n})}function bt(e){return Symbol.asyncIterator&&g(e==null?void 0:e[Symbol.asyncIterator])}function vt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, Array, AsyncIterable, or Iterable.")}function Fr(e,t){if(e!=null){if(dt(e))return _r(e,t);if(Ve(e))return Pe(e,t);if(mt(e))return Hr(e,t);if(bt(e))return kr(e,t);if(ht(e))return jr(e,t)}throw vt(e)}function ge(e,t){return t?Fr(e,t):V(e)}function V(e){if(e instanceof O)return e;if(e!=null){if(dt(e))return oi(e);if(Ve(e))return Wt(e);if(mt(e))return ii(e);if(bt(e))return si(e);if(ht(e))return ai(e)}throw vt(e)}function oi(e){return new O(function(t){var r=e[Se]();if(g(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Wt(e){return new O(function(t){for(var r=0;r<e.length&&!t.closed;r++)t.next(e[r]);t.complete()})}function ii(e){return new O(function(t){e.then(function(r){t.closed||(t.next(r),t.complete())},function(r){return t.error(r)}).then(null,ut)})}function ai(e){return new O(function(t){for(var r=e[De]();!t.closed;){var n=r.next(),o=n.done,i=n.value;o?t.complete():t.next(i)}return function(){return g(r==null?void 0:r.return)&&r.return()}})}function si(e){return new O(function(t){ci(e,t).catch(function(r){return t.error(r)})})}function ci(e,t){var r,n,o,i;return mr(this,void 0,void 0,function(){var a,c;return dr(this,function(u){switch(u.label){case 0:u.trys.push([0,5,6,11]),r=hr(e),u.label=1;case 1:return[4,r.next()];case 2:if(n=u.sent(),!!n.done)return[3,4];a=n.value,t.next(a),u.label=3;case 3:return[3,1];case 4:return[3,11];case 5:return c=u.sent(),o={error:c},[3,11];case 6:return u.trys.push([6,,9,10]),n&&!n.done&&(i=r.return)?[4,i.call(r)]:[3,8];case 7:u.sent(),u.label=8;case 8:return[3,10];case 9:if(o)throw o.error;return[7];case 10:return[7];case 11:return t.complete(),[2]}})})}function de(e,t){return t?Pe(e,t):Wt(e)}function gt(e){return e&&g(e.schedule)}function Nt(e){return e[e.length-1]}function we(e){return g(Nt(e))?e.pop():void 0}function ue(e){return gt(Nt(e))?e.pop():void 0}function yt(e,t){return typeof Nt(e)=="number"?e.pop():t}function H(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e);return r?Pe(e,r):de(e)}function Ir(e){return e instanceof Date&&!isNaN(e)}function f(e,t){return m(function(r,n){var o=0;r.subscribe(new b(n,function(i){n.next(e.call(t,i,o++))}))})}var ui=Array.isArray;function li(e,t){return ui(t)?e.apply(void 0,I([],C(t))):e(t)}function Ue(e){return f(function(t){return li(e,t)})}function X(e,t){return t===void 0&&(t=0),m(function(r,n){r.subscribe(new b(n,function(o){return n.add(e.schedule(function(){return n.next(o)},t))},function(o){return n.add(e.schedule(function(){return n.error(o)},t))},function(){return n.add(e.schedule(function(){return n.complete()},t))}))})}var fi=Array.isArray,pi=Object.getPrototypeOf,mi=Object.prototype,di=Object.keys;function Rr(e){if(e.length===1){var t=e[0];if(fi(t))return{args:t,keys:null};if(hi(t)){var r=di(t);return{args:r.map(function(n){return t[n]}),keys:r}}}return{args:e,keys:null}}function hi(e){return e&&typeof e=="object"&&pi(e)===mi}function $r(e,t){return e.reduce(function(r,n,o){return r[n]=t[o],r},{})}function B(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=we(e),o=Rr(e),i=o.args,a=o.keys;if(i.length===0)return ge([],r);var c=new O(zt(i,r,a?function(u){return $r(a,u)}:se));return n?c.pipe(Ue(n)):c}function zt(e,t,r){return r===void 0&&(r=se),function(n){Pr(t,function(){for(var o=e.length,i=new Array(o),a=o,c=o,u=function(l){Pr(t,function(){var p=ge(e[l],t),d=!1;p.subscribe(new b(n,function(_){i[l]=_,d||(d=!0,c--),c||n.next(r(i.slice()))},void 0,function(){--a||n.complete()}))},n)},s=0;s<o;s++)u(s)},n)}}function Pr(e,t,r){e?r.add(e.schedule(t)):t()}function Vr(e,t,r,n,o,i,a,c){var u=[],s=0,l=0,p=!1,d=function(){p&&!u.length&&!s&&t.complete()},_=function(A){return s<n?$(A):u.push(A)},$=function(A){i&&t.next(A),s++;var Z=!1;V(r(A,l++)).subscribe(new b(t,function(P){o==null||o(P),i?_(P):t.next(P)},void 0,function(){Z=!0},function(){if(Z)try{s--;for(var P=function(){var F=u.shift();a?t.add(a.schedule(function(){return $(F)})):$(F)};u.length&&s<n;)P();d()}catch(F){t.error(F)}}))};return e.subscribe(new b(t,_,void 0,function(){p=!0,d()})),function(){c==null||c()}}function re(e,t,r){return r===void 0&&(r=Infinity),g(t)?re(function(n,o){return f(function(i,a){return t(n,i,o,a)})(V(e(n,o)))},r):(typeof t=="number"&&(r=t),m(function(n,o){return Vr(n,o,e,r)}))}function We(e){return e===void 0&&(e=Infinity),re(se,e)}function Dr(){return We(1)}function et(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Dr()(de(e,ue(e)))}function Ee(e){return new O(function(t){V(e()).subscribe(t)})}var bi=["addListener","removeListener"],vi=["addEventListener","removeEventListener"],gi=["on","off"];function w(e,t,r,n){if(g(r)&&(n=r,r=void 0),n)return w(e,t,r).pipe(Ue(n));var o=C(Si(e)?vi.map(function(c){return function(u){return e[c](t,u,r)}}):yi(e)?bi.map(Ur(e,t)):xi(e)?gi.map(Ur(e,t)):[],2),i=o[0],a=o[1];return!i&&Ve(e)?re(function(c){return w(c,t,r)})(de(e)):new O(function(c){if(!i)throw new TypeError("Invalid event target");var u=function(){for(var s=[],l=0;l<arguments.length;l++)s[l]=arguments[l];return c.next(1<s.length?s:s[0])};return i(u),function(){return a(u)}})}function Ur(e,t){return function(r){return function(n){return e[r](t,n)}}}function yi(e){return g(e.addListener)&&g(e.removeListener)}function xi(e){return g(e.on)&&g(e.off)}function Si(e){return g(e.addEventListener)&&g(e.removeEventListener)}function Wr(e,t,r){e===void 0&&(e=0),r===void 0&&(r=Mr);var n=-1;return t!=null&&(gt(t)?r=t:n=t),new O(function(o){var i=Ir(e)?+e-r.now():e;i<0&&(i=0);var a=0;return r.schedule(function(){o.closed||(o.next(a++),0<=n?this.schedule(void 0,n):o.complete())},i)})}var wi=Array.isArray;function Oe(e){return e.length===1&&wi(e[0])?e[0]:e}function j(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=yt(e,Infinity),o=Oe(e);return o.length?o.length===1?V(o[0]):We(n)(de(o,r)):me}var G=new O(Y);function M(e,t){return m(function(r,n){var o=0;r.subscribe(new b(n,function(i){return e.call(t,i,o++)&&n.next(i)}))})}function Nr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e),n=Oe(e);return n.length?new O(function(o){var i=n.map(function(){return[]}),a=n.map(function(){return!1});o.add(function(){i=a=null});for(var c=function(s){V(n[s]).subscribe(new b(o,function(l){if(i[s].push(l),i.every(function(d){return d.length})){var p=i.map(function(d){return d.shift()});o.next(r?r.apply(void 0,I([],C(p))):p),i.some(function(d,_){return!d.length&&a[_]})&&o.complete()}},void 0,function(){a[s]=!0,!i[s].length&&o.complete()}))},u=0;!o.closed&&u<n.length;u++)c(u);return function(){i=a=null}}):me}function Te(e,t){return t===void 0&&(t=null),t=t!=null?t:e,m(function(r,n){var o=[],i=0;r.subscribe(new b(n,function(a){var c,u,s,l,p=null;i++%t==0&&o.push([]);try{for(var d=ae(o),_=d.next();!_.done;_=d.next()){var $=_.value;$.push(a),e<=$.length&&(p=p!=null?p:[],p.push($))}}catch(P){c={error:P}}finally{try{_&&!_.done&&(u=d.return)&&u.call(d)}finally{if(c)throw c.error}}if(p)try{for(var A=ae(p),Z=A.next();!Z.done;Z=A.next()){var $=Z.value;ve(o,$),n.next($)}}catch(P){s={error:P}}finally{try{Z&&!Z.done&&(l=A.return)&&l.call(A)}finally{if(s)throw s.error}}},void 0,function(){var a,c;try{for(var u=ae(o),s=u.next();!s.done;s=u.next()){var l=s.value;n.next(l)}}catch(p){a={error:p}}finally{try{s&&!s.done&&(c=u.return)&&c.call(u)}finally{if(a)throw a.error}}n.complete()},function(){o=null}))})}function tt(e){return m(function(t,r){var n=null,o=!1,i;n=t.subscribe(new b(r,void 0,function(a){i=V(e(a,tt(e)(t))),n?(n.unsubscribe(),n=null,i.subscribe(r)):o=!0})),o&&(n.unsubscribe(),n=null,i.subscribe(r))})}function zr(e,t,r,n,o){return function(i,a){var c=r,u=t,s=0;i.subscribe(new b(a,function(l){var p=s++;u=c?e(u,l,p):(c=!0,l),n&&a.next(u)},void 0,o&&function(){c&&a.next(u),a.complete()}))}}function Qr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e);return r?Sr(Qr.apply(void 0,I([],C(e))),Ue(r)):m(function(n,o){zt(I([n],C(Oe(e))))(o)})}function Qt(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Qr.apply(void 0,I([],C(e)))}function qr(e,t){return g(t)?re(e,t,1):re(e,1)}function Kr(e,t){return t===void 0&&(t=Ze),m(function(r,n){var o=null,i=null,a=null,c=function(){if(o){o.unsubscribe(),o=null;var s=i;i=null,n.next(s)}};function u(){var s=a+e,l=t.now();if(l<s){o=this.schedule(void 0,s-l);return}c()}r.subscribe(new b(n,function(s){i=s,a=t.now(),o||(o=t.schedule(u,e))},void 0,function(){c(),n.complete()},function(){i=o=null}))})}function xt(e){return e===void 0&&(e=null),m(function(t,r){var n=!1;t.subscribe(new b(r,function(o){n=!0,r.next(o)},void 0,function(){n||r.next(e),r.complete()}))})}function rt(e){return e<=0?function(){return me}:m(function(t,r){var n=0;t.subscribe(new b(r,function(o){++n<=e&&(r.next(o),e<=n&&r.complete())}))})}function Yr(){return m(function(e,t){e.subscribe(new b(t,Y))})}function ce(e){return m(function(t,r){t.subscribe(new b(r,function(){return r.next(e)}))})}function qt(e,t){return t?function(r){return et(t.pipe(rt(1),Yr()),r.pipe(qt(e)))}:re(function(r,n){return e(r,n).pipe(rt(1),ce(r))})}function Me(e,t){t===void 0&&(t=Ze);var r=Wr(e,t);return qt(function(){return r})}function Q(e,t){return t===void 0&&(t=se),e=e!=null?e:Ei,m(function(r,n){var o,i=!0;r.subscribe(new b(n,function(a){var c=t(a);(i||!e(o,c))&&(i=!1,o=c,n.next(a))}))})}function Ei(e,t){return e===t}function W(e,t){return Q(function(r,n){return t?t(r[e],n[e]):r[e]===n[e]})}function D(e){return m(function(t,r){t.subscribe(r),r.add(e)})}function Jr(e){return e<=0?function(){return me}:m(function(t,r){var n=[];t.subscribe(new b(r,function(o){n.push(o),e<n.length&&n.shift()},void 0,function(){var o,i;try{for(var a=ae(n),c=a.next();!c.done;c=a.next()){var u=c.value;r.next(u)}}catch(s){o={error:s}}finally{try{c&&!c.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}r.complete()},function(){n=null}))})}function Oi(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=yt(e,Infinity);return e=Oe(e),m(function(o,i){We(n)(de(I([o],C(e)),r)).subscribe(i)})}function St(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Oi.apply(void 0,I([],C(e)))}function nt(e){return m(function(t,r){var n=!1,o=null;t.subscribe(new b(r,function(a){n=!0,o=a}));var i=function(){if(n){n=!1;var a=o;o=null,r.next(a)}};e.subscribe(new b(r,i,void 0,Y))})}function Xr(e,t){return m(zr(e,t,arguments.length>=2,!0))}function ne(e){e=e||{};var t=e.connector,r=t===void 0?function(){return new T}:t,n=e.resetOnComplete,o=n===void 0?!0:n,i=e.resetOnError,a=i===void 0?!0:i,c=e.resetOnRefCountZero,u=c===void 0?!0:c,s=null,l=null,p=0,d=!1,_=!1,$=function(){s=l=null,d=_=!1};return m(function(A,Z){return p++,l=l!=null?l:r(),l.subscribe(Z),s||(s=ge(A).subscribe({next:function(P){return l.next(P)},error:function(P){_=!0;var F=l;a&&$(),F.error(P)},complete:function(){d=!0;var P=l;o&&$(),P.complete()}})),function(){if(p--,u&&!p&&!_&&!d){var P=s;$(),P==null||P.unsubscribe()}}})}function oe(e,t,r){var n,o,i,a=!1;return e&&typeof e=="object"?(i=(n=e.bufferSize)!==null&&n!==void 0?n:Infinity,t=(o=e.windowTime)!==null&&o!==void 0?o:Infinity,a=!!e.refCount,r=e.scheduler):i=e!=null?e:Infinity,ne({connector:function(){return new lt(i,t,r)},resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:a})}function Kt(e){return M(function(t,r){return e<=r})}function Br(e){return m(function(t,r){var n=!1,o=new b(r,function(){o==null||o.unsubscribe(),n=!0},void 0,Y);V(e).subscribe(o),t.subscribe(new b(r,function(i){return n&&r.next(i)}))})}function U(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e);return m(function(n,o){(r?et(e,n,r):et(e,n)).subscribe(o)})}function x(e,t){return m(function(r,n){var o=null,i=0,a=!1,c=function(){return a&&!o&&n.complete()};r.subscribe(new b(n,function(u){o==null||o.unsubscribe();var s=0,l=i++;V(e(u,l)).subscribe(o=new b(n,function(p){return n.next(t?t(u,p,l,s++):p)},void 0,function(){o=null,c()}))},void 0,function(){a=!0,c()}))})}function Gr(e,t){return t?x(function(){return e},t):x(function(){return e})}function Zr(e){return m(function(t,r){V(e).subscribe(new b(r,function(){return r.complete()},void 0,Y)),!r.closed&&t.subscribe(r)})}function en(e,t){return t===void 0&&(t=!1),m(function(r,n){var o=0;r.subscribe(new b(n,function(i){var a=e(i,o++);(a||t)&&n.next(i),!a&&n.complete()}))})}function k(e,t,r){var n=g(e)||t||r?{next:e,error:t,complete:r}:e;return n?m(function(o,i){o.subscribe(new b(i,function(a){var c;(c=n.next)===null||c===void 0||c.call(n,a),i.next(a)},function(a){var c;(c=n.error)===null||c===void 0||c.call(n,a),i.error(a)},function(){var a;(a=n.complete)===null||a===void 0||a.call(n),i.complete()}))}):se}var Ti={leading:!0,trailing:!1};function tn(e,t){var r=t===void 0?Ti:t,n=r.leading,o=r.trailing;return m(function(i,a){var c=!1,u=null,s=null,l=!1,p=function(){s==null||s.unsubscribe(),s=null,o&&($(),l&&a.complete())},d=function(){s=null,l&&a.complete()},_=function(A){return s=V(e(A)).subscribe(new b(a,p,void 0,d))},$=function(){c&&(a.next(u),!l&&_(u)),c=!1,u=null};i.subscribe(new b(a,function(A){c=!0,u=A,!(s&&!s.closed)&&(n?$():_(A))},void 0,function(){l=!0,!(o&&c&&s&&!s.closed)&&a.complete()}))})}function he(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e);return m(function(n,o){for(var i=e.length,a=new Array(i),c=e.map(function(){return!1}),u=!1,s=function(p){V(e[p]).subscribe(new b(o,function(d){a[p]=d,!u&&!c[p]&&(c[p]=!0,(u=c.every(se))&&(c=null))},void 0,Y))},l=0;l<i;l++)s(l);n.subscribe(new b(o,function(p){if(u){var d=I([p],C(a));o.next(r?r.apply(void 0,I([],C(d))):d)}}))})}function Mi(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return m(function(r,n){Nr.apply(void 0,I([r],C(e))).subscribe(n)})}function rn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Mi.apply(void 0,I([],C(e)))}function nn(){let e=new lt;return w(document,"DOMContentLoaded").pipe(ce(document)).subscribe(e),e}function ie(e,t=document){return t.querySelector(e)||void 0}function le(e,t=document){let r=ie(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function He(){return document.activeElement instanceof HTMLElement?document.activeElement:void 0}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function Ne(e){return document.createElement(e)}function Ce(e,...t){e.replaceWith(...t)}function Ae(e,t=!0){t?e.focus():e.blur()}function on(e){return j(w(e,"focus"),w(e,"blur")).pipe(f(({type:t})=>t==="focus"),U(e===He()))}var an=new T,Ai=Ee(()=>H(new ResizeObserver(e=>{for(let t of e)an.next(t)}))).pipe(x(e=>G.pipe(U(e)).pipe(D(()=>e.disconnect()))),oe(1));function je(e){return{width:e.offsetWidth,height:e.offsetHeight}}function wt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ze(e){return Ai.pipe(k(t=>t.observe(e)),x(t=>an.pipe(M(({target:r})=>r===e),D(()=>t.unobserve(e)),f(({contentRect:r})=>({width:r.width,height:r.height})))),U(je(e)))}function sn(e){return{x:e.scrollLeft,y:e.scrollTop}}function Li(e){return j(w(e,"scroll"),w(window,"resize")).pipe(f(()=>sn(e)),U(sn(e)))}function cn(e,t=16){return Li(e).pipe(f(({y:r})=>{let n=je(e),o=wt(e);return r>=o.height-n.height-t}),Q())}function un(e){if(e instanceof HTMLInputElement)e.select();else throw new Error("Not implemented")}var Et={drawer:le("[data-md-toggle=drawer]"),search:le("[data-md-toggle=search]")};function ln(e){return Et[e].checked}function ke(e,t){Et[e].checked!==t&&Et[e].click()}function Ot(e){let t=Et[e];return w(t,"change").pipe(f(()=>t.checked),U(t.checked))}function _i(e){switch(e.tagName){case"INPUT":case"SELECT":case"TEXTAREA":return!0;default:return e.isContentEditable}}function fn(){return w(window,"keydown").pipe(M(e=>!(e.metaKey||e.ctrlKey)),f(e=>({mode:ln("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),M(({mode:e})=>{if(e==="global"){let t=He();if(typeof t!="undefined")return!_i(t)}return!0}),ne())}function pn(){return new URL(location.href)}function mn(e){location.href=e.href}function dn(){return new T}function hn(){return location.hash.substring(1)}function bn(e){let t=Ne("a");t.href=e,t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Hi(){return w(window,"hashchange").pipe(f(hn),U(hn()),M(e=>e.length>0),ne())}function vn(){return Hi().pipe(x(e=>H(ie(`[id="${e}"]`))))}function Qe(e){let t=matchMedia(e);return w(t,"change").pipe(f(r=>r.matches),U(t.matches))}function gn(){return j(Qe("print").pipe(M(Boolean)),w(window,"beforeprint")).pipe(ce(void 0))}function Yt(e,t){return e.pipe(x(r=>r?t():G))}function Tt(e,t={credentials:"same-origin"}){return ge(fetch(e.toString(),t)).pipe(M(r=>r.status===200))}function Le(e,t){return Tt(e,t).pipe(x(r=>r.json()),oe(1))}function yn(e,t){let r=new DOMParser;return Tt(e,t).pipe(x(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),oe(1))}function xn(){return{x:Math.max(0,pageXOffset),y:Math.max(0,pageYOffset)}}function Jt({x:e,y:t}){window.scrollTo(e||0,t||0)}function Sn(){return j(w(window,"scroll",{passive:!0}),w(window,"resize",{passive:!0})).pipe(f(xn),U(xn()))}function wn(){return{width:innerWidth,height:innerHeight}}function En(){return w(window,"resize",{passive:!0}).pipe(f(wn),U(wn()))}function On(){return B([Sn(),En()]).pipe(f(([e,t])=>({offset:e,size:t})),oe(1))}function Mt(e,{viewport$:t,header$:r}){let n=t.pipe(W("size")),o=B([n,r]).pipe(f(()=>({x:e.offsetLeft,y:e.offsetTop})));return B([r,t,o]).pipe(f(([{height:i},{offset:a,size:c},{x:u,y:s}])=>({offset:{x:a.x-u,y:a.y-s+i},size:c})))}function Tn(e,{tx$:t}){let r=w(e,"message").pipe(f(({data:n})=>n));return t.pipe(tn(()=>r,{leading:!0,trailing:!0}),k(n=>e.postMessage(n)),Gr(r),ne())}var Ci=le("#__config"),qe=JSON.parse(Ci.textContent);qe.base=new URL(qe.base,pn()).toString().replace(/\/$/,"");function ee(){return qe}function At(e){return qe.features.includes(e)}function K(e,t){return typeof t!="undefined"?qe.translations[e].replace("#",t.toString()):qe.translations[e]}function _e(e,t=document){return le(`[data-md-component=${e}]`,t)}function be(e,t=document){return q(`[data-md-component=${e}]`,t)}var so=it(Bt());function Mn(e,t=0){e.setAttribute("tabindex",t.toString())}function An(e){e.removeAttribute("tabindex")}function Ln(e,t){e.setAttribute("data-md-state","lock"),e.style.top=`-${t}px`}function _n(e){let t=-1*parseInt(e.style.top,10);e.removeAttribute("data-md-state"),e.style.top="",t&&window.scrollTo(0,t)}function Hn(e,t){e.setAttribute("data-md-state",t)}function Cn(e){e.removeAttribute("data-md-state")}function jn(e,t){e.classList.toggle("md-nav__link--active",t)}function kn(e){e.classList.remove("md-nav__link--active")}function Fn(e,t){e.firstElementChild.innerHTML=t}function In(e,t){e.setAttribute("data-md-state",t)}function Rn(e){e.removeAttribute("data-md-state")}function $n(e,t){e.setAttribute("data-md-state",t)}function Pn(e){e.removeAttribute("data-md-state")}function Vn(e,t){e.setAttribute("data-md-state",t)}function Dn(e){e.removeAttribute("data-md-state")}function Un(e,t){e.placeholder=t}function Wn(e){e.placeholder=K("search.placeholder")}function Nn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Nn(e,r)}function R(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="boolean"?n.setAttribute(o,t[o]):t[o]&&n.setAttribute(o,"");for(let o of r)Nn(n,o);return n}function zn(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function ye(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ji(e){let t=0;for(let r=0,n=e.length;r<n;r++)t=(t<<5)-t+e.charCodeAt(r),t|=0;return t}function Gt(e){let t=ee();return`${e}[${ji(t.base)}]`}function Qn(e,t){switch(t){case 0:e.textContent=K("search.result.none");break;case 1:e.textContent=K("search.result.one");break;default:e.textContent=K("search.result.other",ye(t))}}function qn(e){e.textContent=K("search.result.placeholder")}function Kn(e,t){e.appendChild(t)}function Yn(e){e.innerHTML=""}function Jn(e,t){e.style.top=`${t}px`}function Xn(e){e.style.top=""}function Bn(e,t){let r=e.firstElementChild;r.style.height=`${t-2*r.offsetTop}px`}function Gn(e){let t=e.firstElementChild;t.style.height=""}function Zn(e,t){e.lastElementChild.appendChild(t)}function eo(e,t){e.lastElementChild.setAttribute("data-md-state",t)}function to(e,t){e.setAttribute("data-md-state",t)}function Zt(e){e.removeAttribute("data-md-state")}function ro(e){return R("button",{class:"md-clipboard md-icon",title:K("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Fe;(function(e){e[e.TEASER=1]="TEASER",e[e.PARENT=2]="PARENT"})(Fe||(Fe={}));function er(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).map(a=>[R("del",null,a)," "]).flat().slice(0,-1),i=e.location;return R("a",{href:i,class:"md-search-result__link",tabIndex:-1},R("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&R("div",{class:"md-search-result__icon md-icon"}),R("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&R("p",{class:"md-search-result__teaser"},zn(e.text,320)),n>0&&o.length>0&&R("p",{class:"md-search-result__terms"},K("search.result.term.missing"),": ",o)))}function no(e){let t=e[0].score,r=[...e],n=r.findIndex(s=>!s.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(s=>s.score<t);i===-1&&(i=r.length);let a=r.slice(0,i),c=r.slice(i),u=[er(o,2|+(!n&&i===0)),...a.map(s=>er(s,1)),...c.length?[R("details",{class:"md-search-result__more"},R("summary",{tabIndex:-1},c.length>0&&c.length===1?K("search.result.more.one"):K("search.result.more.other",c.length)),c.map(s=>er(s,1)))]:[]];return R("li",{class:"md-search-result__item"},u)}function oo(e){return R("ul",{class:"md-source__facts"},e.map(t=>R("li",{class:"md-source__fact"},t)))}function io(e){return R("div",{class:"md-typeset__scrollwrap"},R("div",{class:"md-typeset__table"},e))}function ao(e){let t=ee(),[,r]=t.base.match(/([^/]+)\/?$/),n=e.find(({version:o,aliases:i})=>o===r||i.includes(r))||e[0];return R("div",{class:"md-version"},R("span",{class:"md-version__current"},n.version),R("ul",{class:"md-version__list"},e.map(o=>R("li",{class:"md-version__item"},R("a",{class:"md-version__link",href:`${new URL(o.version,t.base)}`},o.title)))))}var ki=0;function Fi(e,{viewport$:t}){let r=H(e).pipe(x(n=>{let o=n.closest("[data-tabs]");return o instanceof HTMLElement?j(...q("input",o).map(i=>w(i,"change"))):G}));return j(t.pipe(W("size")),r).pipe(f(()=>{let n=je(e);return{scroll:wt(e).width>n.width}}),W("scroll"))}function co(e,t){let r=new T;if(r.pipe(he(Qe("(hover)"))).subscribe(([{scroll:n},o])=>{n&&o?Mn(e):An(e)}),so.default.isSupported()){let n=e.closest("pre");n.id=`__code_${ki++}`,n.insertBefore(ro(n.id),e)}return Fi(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function Ii(e,{target$:t,print$:r}){return t.pipe(f(n=>n.closest("details:not([open])")),M(n=>e===n),St(r),ce(e))}function uo(e,t){let r=new T;return r.subscribe(()=>{e.setAttribute("open",""),e.scrollIntoView()}),Ii(e,t).pipe(k(r),D(()=>r.complete()),ce({ref:e}))}var lo=Ne("table");function fo(e){return Ce(e,lo),Ce(lo,io(e)),H({ref:e})}function po(e,{target$:t,viewport$:r,print$:n}){return j(...q("pre > code",e).map(o=>co(o,{viewport$:r})),...q("table:not([class])",e).map(o=>fo(o)),...q("details",e).map(o=>uo(o,{target$:t,print$:n})))}function Ri(e,{alert$:t}){return t.pipe(x(r=>j(H(!0),H(!1).pipe(Me(2e3))).pipe(f(n=>({message:r,open:n})))))}function mo(e,t){let r=new T;return r.pipe(X(J)).subscribe(({message:n,open:o})=>{Fn(e,n),o?In(e,"open"):Rn(e)}),Ri(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function $i({viewport$:e}){if(!At("header.autohide"))return H(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Te(2,1),f(([o,i])=>[o<i,i]),W(0)),r=B([e,t]).pipe(M(([{offset:o},[,i]])=>Math.abs(i-o.y)>100),f(([,[o]])=>o),Q()),n=Ot("search");return B([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),Q(),x(o=>o?r:H(!1)),U(!1))}function ho(e,t){return Ee(()=>{let r=getComputedStyle(e);return H(r.position==="sticky"||r.position==="-webkit-sticky")}).pipe(Qt(ze(e),$i(t)),f(([r,{height:n},o])=>({height:r?n:0,sticky:r,hidden:o})),Q((r,n)=>r.sticky===n.sticky&&r.height===n.height&&r.hidden===n.hidden),oe(1))}function bo(e,{header$:t,main$:r}){let n=new T;return n.pipe(W("active"),Qt(t),X(J)).subscribe(([{active:o},{hidden:i}])=>{o?$n(e,i?"hidden":"shadow"):Pn(e)}),r.subscribe(o=>n.next(o)),t.pipe(f(o=>N({ref:e},o)))}function Pi(e,{viewport$:t,header$:r}){return Mt(e,{header$:r,viewport$:t}).pipe(f(({offset:{y:n}})=>{let{height:o}=je(e);return{active:n>=o}}),W("active"))}function vo(e,t){let r=new T;r.pipe(X(J)).subscribe(({active:o})=>{o?Vn(e,"active"):Dn(e)});let n=ie("article h1");return typeof n=="undefined"?G:Pi(n,t).pipe(k(r),D(()=>r.complete()),f(o=>N({ref:e},o)))}function go(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),Q()),o=n.pipe(x(()=>ze(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),W("bottom"))));return B([n,o,t]).pipe(f(([i,{top:a,bottom:c},{offset:{y:u},size:{height:s}}])=>(s=Math.max(0,s-Math.max(0,a-u,i)-Math.max(0,s+u-c)),{offset:a-i,height:s,active:a-i<=u})),Q((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}var tr=it(Bt());function yo({alert$:e}){tr.default.isSupported()&&new O(t=>{new tr.default("[data-clipboard-target], [data-clipboard-text]").on("success",r=>t.next(r))}).subscribe(()=>e.next(K("clipboard.copied")))}function Vi(e){if(e.length<2)return e;let[t,r]=e.sort((i,a)=>i.length-a.length).map(i=>i.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;let o=ee();return e.map(i=>i.replace(t.slice(0,n),`${o.base}/`))}function xo({document$:e,location$:t,viewport$:r}){let n=ee();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",w(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=ie("link[rel='shortcut icon']");typeof o!="undefined"&&(o.href=o.href);let i=yn(`${n.base}/sitemap.xml`).pipe(f(s=>Vi(q("loc",s).map(l=>l.textContent))),x(s=>w(document.body,"click").pipe(M(l=>!l.metaKey&&!l.ctrlKey),x(l=>{if(l.target instanceof Element){let p=l.target.closest("a");if(p&&!p.target&&s.includes(p.href))return l.preventDefault(),H({url:new URL(p.href)})}return G}))),ne()),a=w(window,"popstate").pipe(M(s=>s.state!==null),f(s=>({url:new URL(location.href),offset:s.state})),ne());j(i,a).pipe(Q((s,l)=>s.url.href===l.url.href),f(({url:s})=>s)).subscribe(t);let c=t.pipe(W("pathname"),x(s=>Tt(s.href).pipe(tt(()=>(mn(s),G)))),ne());i.pipe(nt(c)).subscribe(({url:s})=>{history.pushState({},"",s.toString())});let u=new DOMParser;c.pipe(x(s=>s.text()),f(s=>u.parseFromString(s,"text/html"))).subscribe(e),j(i,a).pipe(nt(e)).subscribe(({url:s,offset:l})=>{s.hash&&!l?bn(s.hash):Jt(l||{y:0})}),e.pipe(Kt(1)).subscribe(s=>{for(let l of["title","link[rel='canonical']","meta[name='author']","meta[name='description']","[data-md-component=announce]","[data-md-component=header-topic]","[data-md-component=container]","[data-md-component=skip]"]){let p=ie(l),d=ie(l,s);typeof p!="undefined"&&typeof d!="undefined"&&Ce(p,d)}}),e.pipe(Kt(1),f(()=>_e("container")),x(s=>H(...q("script",s))),qr(s=>{let l=Ne("script");if(s.src){for(let p of s.getAttributeNames())l.setAttribute(p,s.getAttribute(p));return Ce(s,l),new O(p=>{l.onload=()=>p.complete()})}else return l.textContent=s.textContent,Ce(s,l),me})).subscribe(),r.pipe(Br(i),Kr(250),W("offset")).subscribe(({offset:s})=>{history.replaceState(s,"")}),j(i,a).pipe(Te(2,1),M(([s,l])=>s.url.pathname===l.url.pathname),f(([,s])=>s)).subscribe(({offset:s})=>{Jt(s||{y:0})})}var Wi=it(wo());function Eo(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}var xe;(function(e){e[e.SETUP=0]="SETUP",e[e.READY=1]="READY",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"})(xe||(xe={}));function Oo(e){return e.type===1}function To(e){return e.type===2}function Lt(e){return e.type===3}function Ni({config:e,docs:t,index:r}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[K("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=K("search.config.separator"));let n=K("search.config.pipeline").split(/\s*,\s*/).filter(Boolean);return{config:e,docs:t,index:r,pipeline:n}}function Mo(e,t){let r=ee(),n=new Worker(e),o=new T,i=Tn(n,{tx$:o}).pipe(f(a=>{if(Lt(a))for(let c of a.data)for(let u of c)u.location=`${r.base}/${u.location}`;return a}),ne());return ge(t).pipe(f(a=>({type:xe.SETUP,data:Ni(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function Ao(){let e=ee();Le(new URL("versions.json",e.base)).subscribe(t=>{le(".md-header__topic").appendChild(ao(t))})}function zi(e){let t=(__search==null?void 0:__search.transform)||Eo,r=on(e),n=j(w(e,"keyup"),w(e,"focus").pipe(Me(1))).pipe(f(()=>t(e.value)),Q());return B([n,r]).pipe(f(([o,i])=>({value:o,focus:i})))}function Lo(e,{tx$:t}){let r=new T;return r.pipe(W("value"),f(({value:n})=>({type:xe.QUERY,data:n}))).subscribe(t.next.bind(t)),r.pipe(W("focus")).subscribe(({focus:n})=>{n?(ke("search",n),Un(e,"")):Wn(e)}),w(e.form,"reset").pipe(Zr(r.pipe(Jr(1)))).subscribe(()=>Ae(e)),zi(e).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function _o(e,{rx$:t},{query$:r}){let n=new T,o=cn(e.parentElement).pipe(M(Boolean)),i=le(":scope > :first-child",e);n.pipe(X(J),he(r)).subscribe(([{data:u},{value:s}])=>{s?Qn(i,u.length):qn(i)});let a=le(":scope > :last-child",e);return n.pipe(X(J),k(()=>Yn(a)),x(({data:u})=>j(H(...u.slice(0,10)),H(...u.slice(10)).pipe(Te(4),rn(o),x(([s])=>H(...s)))))).subscribe(u=>{Kn(a,no(u))}),t.pipe(M(Lt),f(({data:u})=>({data:u})),U({data:[]})).pipe(k(n),D(()=>n.complete()),f(u=>N({ref:e},u)))}function Ho(e,{index$:t,keyboard$:r}){let n=ee(),o=Mo(n.search,t),i=_e("search-query",e),a=_e("search-result",e),{tx$:c,rx$:u}=o;c.pipe(M(To),nt(u.pipe(M(Oo))),rt(1)).subscribe(c.next.bind(c)),r.pipe(M(({mode:l})=>l==="search")).subscribe(l=>{let p=He();switch(l.type){case"Enter":p===i&&l.claim();break;case"Escape":case"Tab":ke("search",!1),Ae(i,!1);break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")Ae(i);else{let d=[i,...q(":not(details) > [href], summary, details[open] [href]",a)],_=Math.max(0,(Math.max(0,d.indexOf(p))+d.length+(l.type==="ArrowUp"?-1:1))%d.length);Ae(d[_])}l.claim();break;default:i!==He()&&Ae(i)}}),r.pipe(M(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":Ae(i),un(i),l.claim();break}});let s=Lo(i,o);return j(s,_o(a,o,{query$:s}))}function Qi(e,{viewport$:t,main$:r}){let n=e.parentElement.offsetTop-e.parentElement.parentElement.offsetTop;return B([r,t]).pipe(f(([{offset:o,height:i},{offset:{y:a}}])=>(i=i+Math.min(n,Math.max(0,a-o))-n,{height:i,locked:a>=o+n})),Q((o,i)=>o.height===i.height&&o.locked===i.locked))}function rr(e,n){var{header$:t}=n,r=lr(n,["header$"]);let o=new T;return o.pipe(X(J),he(t)).subscribe({next([{height:i},{height:a}]){Bn(e,i),Jn(e,a)},complete(){Xn(e),Gn(e)}}),Qi(e,r).pipe(k(o),D(()=>o.complete()),f(i=>N({ref:e},i)))}function Co(e,t){let r=typeof t!="undefined"?`https://api.github.com/repos/${e}/${t}`:`https://api.github.com/users/${e}`;return Le(r).pipe(f(n=>{if(typeof t!="undefined"){let{stargazers_count:o,forks_count:i}=n;return[`${ye(o)} Stars`,`${ye(i)} Forks`]}else{let{public_repos:o}=n;return[`${ye(o)} Repositories`]}}),xt([]))}function jo(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Le(r).pipe(f(({star_count:n,forks_count:o})=>[`${ye(n)} Stars`,`${ye(o)} Forks`]),xt([]))}function ko(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return Co(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return jo(o,i);default:return G}}var qi;function Ki(e){return qi||(qi=Ee(()=>{let t=sessionStorage.getItem(Gt("__repo"));if(t)return H(JSON.parse(t));{let r=ko(e.href);return r.subscribe(n=>{try{sessionStorage.setItem(Gt("__repo"),JSON.stringify(n))}catch(o){}}),r}}).pipe(tt(()=>G),M(t=>t.length>0),f(t=>({facts:t})),oe(1)))}function Fo(e){let t=new T;return t.subscribe(({facts:r})=>{Zn(e,oo(r)),eo(e,"done")}),Ki(e).pipe(k(t),D(()=>t.complete()),f(r=>N({ref:e},r)))}function Yi(e,{viewport$:t,header$:r}){return Mt(e,{header$:r,viewport$:t}).pipe(f(({offset:{y:n}})=>({hidden:n>=10})),W("hidden"))}function Io(e,t){let r=new T;return r.pipe(X(J)).subscribe({next({hidden:n}){n?to(e,"hidden"):Zt(e)},complete(){Zt(e)}}),Yi(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function Ji(e,{viewport$:t,header$:r}){let n=new Map;for(let a of e){let c=decodeURIComponent(a.hash.substring(1)),u=ie(`[id="${c}"]`);typeof u!="undefined"&&n.set(a,u)}let o=r.pipe(f(a=>24+a.height));return ze(document.body).pipe(W("height"),f(()=>{let a=[];return[...n].reduce((c,[u,s])=>{for(;a.length&&n.get(a[a.length-1]).tagName>=s.tagName;)a.pop();let l=s.offsetTop;for(;!l&&s.parentElement;)s=s.parentElement,l=s.offsetTop;return c.set([...a=[...a,u]].reverse(),l)},new Map)}),x(a=>B([o,t]).pipe(Xr(([c,u],[s,{offset:{y:l}}])=>{for(;u.length;){let[,p]=u[0];if(p-s<l)c=[...c,u.shift()];else break}for(;c.length;){let[,p]=c[c.length-1];if(p-s>=l)u=[c.pop(),...u];else break}return[c,u]},[[],[...a]]),Q((c,u)=>c[0]===u[0]&&c[1]===u[1])))).pipe(f(([a,c])=>({prev:a.map(([u])=>u),next:c.map(([u])=>u)})),U({prev:[],next:[]}),Te(2,1),f(([a,c])=>a.prev.length<c.prev.length?{prev:c.prev.slice(Math.max(0,a.prev.length-1),c.prev.length),next:[]}:{prev:c.prev.slice(-1),next:c.next.slice(0,c.next.length-a.next.length)}))}function Ro(e,t){let r=new T;r.pipe(X(J)).subscribe(({prev:o,next:i})=>{for(let[a]of i)kn(a),Cn(a);for(let[a,[c]]of o.entries())jn(c,a===o.length-1),Hn(c,"blur")});let n=q("[href^=\\#]",e);return Ji(n,t).pipe(k(r),D(()=>r.complete()),f(o=>N({ref:e},o)))}function $o({document$:e,tablet$:t}){e.pipe(x(()=>H(...q("[data-md-state=indeterminate]"))),k(r=>{r.indeterminate=!0,r.checked=!1}),re(r=>w(r,"change").pipe(en(()=>r.hasAttribute("data-md-state")),ce(r))),he(t)).subscribe(([r,n])=>{r.removeAttribute("data-md-state"),n&&(r.checked=!1)})}function Xi(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Po({document$:e}){e.pipe(x(()=>H(...q("[data-md-scrollfix]"))),k(t=>t.removeAttribute("data-md-scrollfix")),M(Xi),re(t=>w(t,"touchstart").pipe(ce(t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Vo({viewport$:e,tablet$:t}){B([Ot("search"),t]).pipe(f(([r,n])=>r&&!n),x(r=>H(r).pipe(Me(r?400:100),X(J))),he(e)).subscribe(([r,{offset:{y:n}}])=>{r?Ln(document.body,n):_n(document.body)})}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var Ke=nn(),nr=dn(),or=vn(),ir=fn(),fe=On(),_t=Qe("(min-width: 960px)"),Do=Qe("(min-width: 1220px)"),Uo=gn(),Wo=ee(),Bi=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||Le(`${Wo.base}/search/search_index.json`):G,ar=new T;yo({alert$:ar});At("navigation.instant")&&xo({document$:Ke,location$:nr,viewport$:fe});var No;((No=Wo.version)==null?void 0:No.provider)==="mike"&&Ao();j(nr,or).pipe(Me(125)).subscribe(()=>{ke("drawer",!1),ke("search",!1)});ir.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ie("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=ie("[href][rel=next]");typeof r!="undefined"&&r.click();break}});$o({document$:Ke,tablet$:_t});Po({document$:Ke});Vo({viewport$:fe,tablet$:_t});var Ie=ho(_e("header"),{viewport$:fe}),sr=Ke.pipe(f(()=>_e("main")),x(e=>go(e,{viewport$:fe,header$:Ie})),oe(1)),Gi=j(...be("dialog").map(e=>mo(e,{alert$:ar})),...be("header").map(e=>bo(e,{viewport$:fe,header$:Ie,main$:sr})),...be("search").map(e=>Ho(e,{index$:Bi,keyboard$:ir})),...be("source").map(e=>Fo(e)),...be("tabs").map(e=>Io(e,{viewport$:fe,header$:Ie}))),Zi=Ee(()=>j(...be("content").map(e=>po(e,{target$:or,viewport$:fe,print$:Uo})),...be("header-title").map(e=>vo(e,{viewport$:fe,header$:Ie})),...be("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Yt(Do,()=>rr(e,{viewport$:fe,header$:Ie,main$:sr})):Yt(_t,()=>rr(e,{viewport$:fe,header$:Ie,main$:sr}))),...be("toc").map(e=>Ro(e,{viewport$:fe,header$:Ie})))),zo=Ke.pipe(x(()=>Zi),St(Gi),oe(1));zo.subscribe();window.document$=Ke;window.location$=nr;window.target$=or;window.keyboard$=ir;window.viewport$=fe;window.tablet$=_t;window.screen$=Do;window.print$=Uo;window.alert$=ar;window.component$=zo;})();
-/*!
- * clipboard.js v2.0.6
- * https://clipboardjs.com/
- * 
- * Licensed MIT © Zeno Rocha
- */
-/*!
- * escape-html
- * Copyright(c) 2012-2013 TJ Holowaychuk
- * Copyright(c) 2015 Andreas Lubbe
- * Copyright(c) 2015 Tiancheng "Timothy" Gu
- * MIT Licensed
- */
-/*! *****************************************************************************
-Copyright (c) Microsoft Corporation.
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THIS SOFTWARE.
-***************************************************************************** */
-//# sourceMappingURL=bundle.5cf3e710.min.js.map
-
diff --git a/5.4/assets/javascripts/bundle.5cf3e710.min.js.map b/5.4/assets/javascripts/bundle.5cf3e710.min.js.map
deleted file mode 100644 (file)
index 2b36e8c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "version": 3,
-  "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/util/caughtSchedule.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/fromArray.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/switchMapTo.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/size/index.ts", "src/assets/javascripts/browser/element/offset/index.ts", "src/assets/javascripts/browser/element/selection/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/index.ts", "src/assets/javascripts/actions/_/index.ts", "src/assets/javascripts/actions/anchor/index.ts", "src/assets/javascripts/actions/dialog/index.ts", "src/assets/javascripts/actions/header/_/index.ts", "src/assets/javascripts/actions/header/title/index.ts", "src/assets/javascripts/actions/search/query/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/actions/search/result/index.ts", "src/assets/javascripts/actions/sidebar/index.ts", "src/assets/javascripts/actions/source/index.ts", "src/assets/javascripts/actions/tabs/index.ts", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts"],
-  "sourcesContent": ["(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (factory());\n}(this, (function () { 'use strict';\n\n  /**\n   * Applies the :focus-visible polyfill at the given scope.\n   * A scope in this case is either the top-level Document or a Shadow Root.\n   *\n   * @param {(Document|ShadowRoot)} scope\n   * @see https://github.com/WICG/focus-visible\n   */\n  function applyFocusVisiblePolyfill(scope) {\n    var hadKeyboardEvent = true;\n    var hadFocusVisibleRecently = false;\n    var hadFocusVisibleRecentlyTimeout = null;\n\n    var inputTypesAllowlist = {\n      text: true,\n      search: true,\n      url: true,\n      tel: true,\n      email: true,\n      password: true,\n      number: true,\n      date: true,\n      month: true,\n      week: true,\n      time: true,\n      datetime: true,\n      'datetime-local': true\n    };\n\n    /**\n     * Helper function for legacy browsers and iframes which sometimes focus\n     * elements like document, body, and non-interactive SVG.\n     * @param {Element} el\n     */\n    function isValidFocusTarget(el) {\n      if (\n        el &&\n        el !== document &&\n        el.nodeName !== 'HTML' &&\n        el.nodeName !== 'BODY' &&\n        'classList' in el &&\n        'contains' in el.classList\n      ) {\n        return true;\n      }\n      return false;\n    }\n\n    /**\n     * Computes whether the given element should automatically trigger the\n     * `focus-visible` class being added, i.e. whether it should always match\n     * `:focus-visible` when focused.\n     * @param {Element} el\n     * @return {boolean}\n     */\n    function focusTriggersKeyboardModality(el) {\n      var type = el.type;\n      var tagName = el.tagName;\n\n      if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n        return true;\n      }\n\n      if (tagName === 'TEXTAREA' && !el.readOnly) {\n        return true;\n      }\n\n      if (el.isContentEditable) {\n        return true;\n      }\n\n      return false;\n    }\n\n    /**\n     * Add the `focus-visible` class to the given element if it was not added by\n     * the author.\n     * @param {Element} el\n     */\n    function addFocusVisibleClass(el) {\n      if (el.classList.contains('focus-visible')) {\n        return;\n      }\n      el.classList.add('focus-visible');\n      el.setAttribute('data-focus-visible-added', '');\n    }\n\n    /**\n     * Remove the `focus-visible` class from the given element if it was not\n     * originally added by the author.\n     * @param {Element} el\n     */\n    function removeFocusVisibleClass(el) {\n      if (!el.hasAttribute('data-focus-visible-added')) {\n        return;\n      }\n      el.classList.remove('focus-visible');\n      el.removeAttribute('data-focus-visible-added');\n    }\n\n    /**\n     * If the most recent user interaction was via the keyboard;\n     * and the key press did not include a meta, alt/option, or control key;\n     * then the modality is keyboard. Otherwise, the modality is not keyboard.\n     * Apply `focus-visible` to any current active element and keep track\n     * of our keyboard modality state with `hadKeyboardEvent`.\n     * @param {KeyboardEvent} e\n     */\n    function onKeyDown(e) {\n      if (e.metaKey || e.altKey || e.ctrlKey) {\n        return;\n      }\n\n      if (isValidFocusTarget(scope.activeElement)) {\n        addFocusVisibleClass(scope.activeElement);\n      }\n\n      hadKeyboardEvent = true;\n    }\n\n    /**\n     * If at any point a user clicks with a pointing device, ensure that we change\n     * the modality away from keyboard.\n     * This avoids the situation where a user presses a key on an already focused\n     * element, and then clicks on a different element, focusing it with a\n     * pointing device, while we still think we're in keyboard modality.\n     * @param {Event} e\n     */\n    function onPointerDown(e) {\n      hadKeyboardEvent = false;\n    }\n\n    /**\n     * On `focus`, add the `focus-visible` class to the target if:\n     * - the target received focus as a result of keyboard navigation, or\n     * - the event target is an element that will likely require interaction\n     *   via the keyboard (e.g. a text box)\n     * @param {Event} e\n     */\n    function onFocus(e) {\n      // Prevent IE from focusing the document or HTML element.\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n        addFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * On `blur`, remove the `focus-visible` class from the target.\n     * @param {Event} e\n     */\n    function onBlur(e) {\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (\n        e.target.classList.contains('focus-visible') ||\n        e.target.hasAttribute('data-focus-visible-added')\n      ) {\n        // To detect a tab/window switch, we look for a blur event followed\n        // rapidly by a visibility change.\n        // If we don't see a visibility change within 100ms, it's probably a\n        // regular focus change.\n        hadFocusVisibleRecently = true;\n        window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n        hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n          hadFocusVisibleRecently = false;\n        }, 100);\n        removeFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * If the user changes tabs, keep track of whether or not the previously\n     * focused element had .focus-visible.\n     * @param {Event} e\n     */\n    function onVisibilityChange(e) {\n      if (document.visibilityState === 'hidden') {\n        // If the tab becomes active again, the browser will handle calling focus\n        // on the element (Safari actually calls it twice).\n        // If this tab change caused a blur on an element with focus-visible,\n        // re-apply the class when the user switches back to the tab.\n        if (hadFocusVisibleRecently) {\n          hadKeyboardEvent = true;\n        }\n        addInitialPointerMoveListeners();\n      }\n    }\n\n    /**\n     * Add a group of listeners to detect usage of any pointing devices.\n     * These listeners will be added when the polyfill first loads, and anytime\n     * the window is blurred, so that they are active when the window regains\n     * focus.\n     */\n    function addInitialPointerMoveListeners() {\n      document.addEventListener('mousemove', onInitialPointerMove);\n      document.addEventListener('mousedown', onInitialPointerMove);\n      document.addEventListener('mouseup', onInitialPointerMove);\n      document.addEventListener('pointermove', onInitialPointerMove);\n      document.addEventListener('pointerdown', onInitialPointerMove);\n      document.addEventListener('pointerup', onInitialPointerMove);\n      document.addEventListener('touchmove', onInitialPointerMove);\n      document.addEventListener('touchstart', onInitialPointerMove);\n      document.addEventListener('touchend', onInitialPointerMove);\n    }\n\n    function removeInitialPointerMoveListeners() {\n      document.removeEventListener('mousemove', onInitialPointerMove);\n      document.removeEventListener('mousedown', onInitialPointerMove);\n      document.removeEventListener('mouseup', onInitialPointerMove);\n      document.removeEventListener('pointermove', onInitialPointerMove);\n      document.removeEventListener('pointerdown', onInitialPointerMove);\n      document.removeEventListener('pointerup', onInitialPointerMove);\n      document.removeEventListener('touchmove', onInitialPointerMove);\n      document.removeEventListener('touchstart', onInitialPointerMove);\n      document.removeEventListener('touchend', onInitialPointerMove);\n    }\n\n    /**\n     * When the polfyill first loads, assume the user is in keyboard modality.\n     * If any event is received from a pointing device (e.g. mouse, pointer,\n     * touch), turn off keyboard modality.\n     * This accounts for situations where focus enters the page from the URL bar.\n     * @param {Event} e\n     */\n    function onInitialPointerMove(e) {\n      // Work around a Safari quirk that fires a mousemove on <html> whenever the\n      // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n      if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n        return;\n      }\n\n      hadKeyboardEvent = false;\n      removeInitialPointerMoveListeners();\n    }\n\n    // For some kinds of state, we are interested in changes at the global scope\n    // only. For example, global pointer input, global key presses and global\n    // visibility change should affect the state at every scope:\n    document.addEventListener('keydown', onKeyDown, true);\n    document.addEventListener('mousedown', onPointerDown, true);\n    document.addEventListener('pointerdown', onPointerDown, true);\n    document.addEventListener('touchstart', onPointerDown, true);\n    document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n    addInitialPointerMoveListeners();\n\n    // For focus and blur, we specifically care about state changes in the local\n    // scope. This is because focus / blur events that originate from within a\n    // shadow root are not re-dispatched from the host element if it was already\n    // the active element in its own scope:\n    scope.addEventListener('focus', onFocus, true);\n    scope.addEventListener('blur', onBlur, true);\n\n    // We detect that a node is a ShadowRoot by ensuring that it is a\n    // DocumentFragment and also has a host property. This check covers native\n    // implementation and polyfill implementation transparently. If we only cared\n    // about the native implementation, we could just check if the scope was\n    // an instance of a ShadowRoot.\n    if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n      // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n      // have a root element to add a class to. So, we add this attribute to the\n      // host element instead:\n      scope.host.setAttribute('data-js-focus-visible', '');\n    } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n      document.documentElement.classList.add('js-focus-visible');\n      document.documentElement.setAttribute('data-js-focus-visible', '');\n    }\n  }\n\n  // It is important to wrap all references to global window and document in\n  // these checks to support server-side rendering use cases\n  // @see https://github.com/WICG/focus-visible/issues/199\n  if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n    // Make the polyfill helper globally available. This can be used as a signal\n    // to interested libraries that wish to coordinate with the polyfill for e.g.,\n    // applying the polyfill to a shadow root:\n    window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n    // Notify interested libraries of the polyfill's presence, in case the\n    // polyfill was loaded lazily:\n    var event;\n\n    try {\n      event = new CustomEvent('focus-visible-polyfill-ready');\n    } catch (error) {\n      // IE11 does not support using CustomEvent as a constructor directly:\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n    }\n\n    window.dispatchEvent(event);\n  }\n\n  if (typeof document !== 'undefined') {\n    // Apply the polyfill to the global document, so that no JavaScript\n    // coordination is required to use the polyfill in the top-level document:\n    applyFocusVisiblePolyfill(document);\n  }\n\n})));\n", "/*!\n * clipboard.js v2.0.6\n * https://clipboardjs.com/\n * \n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 6);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ (function(module, exports) {\n\nfunction select(element) {\n    var selectedText;\n\n    if (element.nodeName === 'SELECT') {\n        element.focus();\n\n        selectedText = element.value;\n    }\n    else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n        var isReadOnly = element.hasAttribute('readonly');\n\n        if (!isReadOnly) {\n            element.setAttribute('readonly', '');\n        }\n\n        element.select();\n        element.setSelectionRange(0, element.value.length);\n\n        if (!isReadOnly) {\n            element.removeAttribute('readonly');\n        }\n\n        selectedText = element.value;\n    }\n    else {\n        if (element.hasAttribute('contenteditable')) {\n            element.focus();\n        }\n\n        var selection = window.getSelection();\n        var range = document.createRange();\n\n        range.selectNodeContents(element);\n        selection.removeAllRanges();\n        selection.addRange(range);\n\n        selectedText = selection.toString();\n    }\n\n    return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n/* 1 */\n/***/ (function(module, exports) {\n\nfunction E () {\n  // Keep this empty so it's easier to inherit from\n  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n  on: function (name, callback, ctx) {\n    var e = this.e || (this.e = {});\n\n    (e[name] || (e[name] = [])).push({\n      fn: callback,\n      ctx: ctx\n    });\n\n    return this;\n  },\n\n  once: function (name, callback, ctx) {\n    var self = this;\n    function listener () {\n      self.off(name, listener);\n      callback.apply(ctx, arguments);\n    };\n\n    listener._ = callback\n    return this.on(name, listener, ctx);\n  },\n\n  emit: function (name) {\n    var data = [].slice.call(arguments, 1);\n    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n    var i = 0;\n    var len = evtArr.length;\n\n    for (i; i < len; i++) {\n      evtArr[i].fn.apply(evtArr[i].ctx, data);\n    }\n\n    return this;\n  },\n\n  off: function (name, callback) {\n    var e = this.e || (this.e = {});\n    var evts = e[name];\n    var liveEvents = [];\n\n    if (evts && callback) {\n      for (var i = 0, len = evts.length; i < len; i++) {\n        if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n          liveEvents.push(evts[i]);\n      }\n    }\n\n    // Remove event from queue to prevent memory leak\n    // Suggested by https://github.com/lazd\n    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n    (liveEvents.length)\n      ? e[name] = liveEvents\n      : delete e[name];\n\n    return this;\n  }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ }),\n/* 2 */\n/***/ (function(module, exports, __webpack_require__) {\n\nvar is = __webpack_require__(3);\nvar delegate = __webpack_require__(4);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n    if (!target && !type && !callback) {\n        throw new Error('Missing required arguments');\n    }\n\n    if (!is.string(type)) {\n        throw new TypeError('Second argument must be a String');\n    }\n\n    if (!is.fn(callback)) {\n        throw new TypeError('Third argument must be a Function');\n    }\n\n    if (is.node(target)) {\n        return listenNode(target, type, callback);\n    }\n    else if (is.nodeList(target)) {\n        return listenNodeList(target, type, callback);\n    }\n    else if (is.string(target)) {\n        return listenSelector(target, type, callback);\n    }\n    else {\n        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n    }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n    node.addEventListener(type, callback);\n\n    return {\n        destroy: function() {\n            node.removeEventListener(type, callback);\n        }\n    }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n    Array.prototype.forEach.call(nodeList, function(node) {\n        node.addEventListener(type, callback);\n    });\n\n    return {\n        destroy: function() {\n            Array.prototype.forEach.call(nodeList, function(node) {\n                node.removeEventListener(type, callback);\n            });\n        }\n    }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n    return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n/* 3 */\n/***/ (function(module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n    return value !== undefined\n        && value instanceof HTMLElement\n        && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return value !== undefined\n        && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n        && ('length' in value)\n        && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n    return typeof value === 'string'\n        || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return type === '[object Function]';\n};\n\n\n/***/ }),\n/* 4 */\n/***/ (function(module, exports, __webpack_require__) {\n\nvar closest = __webpack_require__(5);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n    var listenerFn = listener.apply(this, arguments);\n\n    element.addEventListener(type, listenerFn, useCapture);\n\n    return {\n        destroy: function() {\n            element.removeEventListener(type, listenerFn, useCapture);\n        }\n    }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n    // Handle the regular Element usage\n    if (typeof elements.addEventListener === 'function') {\n        return _delegate.apply(null, arguments);\n    }\n\n    // Handle Element-less usage, it defaults to global delegation\n    if (typeof type === 'function') {\n        // Use `document` as the first parameter, then apply arguments\n        // This is a short way to .unshift `arguments` without running into deoptimizations\n        return _delegate.bind(null, document).apply(null, arguments);\n    }\n\n    // Handle Selector-based usage\n    if (typeof elements === 'string') {\n        elements = document.querySelectorAll(elements);\n    }\n\n    // Handle Array-like based usage\n    return Array.prototype.map.call(elements, function (element) {\n        return _delegate(element, selector, type, callback, useCapture);\n    });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n    return function(e) {\n        e.delegateTarget = closest(e.target, selector);\n\n        if (e.delegateTarget) {\n            callback.call(element, e);\n        }\n    }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n/* 5 */\n/***/ (function(module, exports) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n    var proto = Element.prototype;\n\n    proto.matches = proto.matchesSelector ||\n                    proto.mozMatchesSelector ||\n                    proto.msMatchesSelector ||\n                    proto.oMatchesSelector ||\n                    proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n    while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n        if (typeof element.matches === 'function' &&\n            element.matches(selector)) {\n          return element;\n        }\n        element = element.parentNode;\n    }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n/* 6 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n__webpack_require__.r(__webpack_exports__);\n\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(0);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n\n// CONCATENATED MODULE: ./src/clipboard-action.js\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\n/**\n * Inner class which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n */\n\nvar clipboard_action_ClipboardAction = function () {\n    /**\n     * @param {Object} options\n     */\n    function ClipboardAction(options) {\n        _classCallCheck(this, ClipboardAction);\n\n        this.resolveOptions(options);\n        this.initSelection();\n    }\n\n    /**\n     * Defines base properties passed from constructor.\n     * @param {Object} options\n     */\n\n\n    _createClass(ClipboardAction, [{\n        key: 'resolveOptions',\n        value: function resolveOptions() {\n            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n            this.action = options.action;\n            this.container = options.container;\n            this.emitter = options.emitter;\n            this.target = options.target;\n            this.text = options.text;\n            this.trigger = options.trigger;\n\n            this.selectedText = '';\n        }\n\n        /**\n         * Decides which selection strategy is going to be applied based\n         * on the existence of `text` and `target` properties.\n         */\n\n    }, {\n        key: 'initSelection',\n        value: function initSelection() {\n            if (this.text) {\n                this.selectFake();\n            } else if (this.target) {\n                this.selectTarget();\n            }\n        }\n\n        /**\n         * Creates a fake textarea element, sets its value from `text` property,\n         * and makes a selection on it.\n         */\n\n    }, {\n        key: 'selectFake',\n        value: function selectFake() {\n            var _this = this;\n\n            var isRTL = document.documentElement.getAttribute('dir') == 'rtl';\n\n            this.removeFake();\n\n            this.fakeHandlerCallback = function () {\n                return _this.removeFake();\n            };\n            this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;\n\n            this.fakeElem = document.createElement('textarea');\n            // Prevent zooming on iOS\n            this.fakeElem.style.fontSize = '12pt';\n            // Reset box model\n            this.fakeElem.style.border = '0';\n            this.fakeElem.style.padding = '0';\n            this.fakeElem.style.margin = '0';\n            // Move element out of screen horizontally\n            this.fakeElem.style.position = 'absolute';\n            this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';\n            // Move element to the same position vertically\n            var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n            this.fakeElem.style.top = yPosition + 'px';\n\n            this.fakeElem.setAttribute('readonly', '');\n            this.fakeElem.value = this.text;\n\n            this.container.appendChild(this.fakeElem);\n\n            this.selectedText = select_default()(this.fakeElem);\n            this.copyText();\n        }\n\n        /**\n         * Only removes the fake element after another click event, that way\n         * a user can hit `Ctrl+C` to copy because selection still exists.\n         */\n\n    }, {\n        key: 'removeFake',\n        value: function removeFake() {\n            if (this.fakeHandler) {\n                this.container.removeEventListener('click', this.fakeHandlerCallback);\n                this.fakeHandler = null;\n                this.fakeHandlerCallback = null;\n            }\n\n            if (this.fakeElem) {\n                this.container.removeChild(this.fakeElem);\n                this.fakeElem = null;\n            }\n        }\n\n        /**\n         * Selects the content from element passed on `target` property.\n         */\n\n    }, {\n        key: 'selectTarget',\n        value: function selectTarget() {\n            this.selectedText = select_default()(this.target);\n            this.copyText();\n        }\n\n        /**\n         * Executes the copy operation based on the current selection.\n         */\n\n    }, {\n        key: 'copyText',\n        value: function copyText() {\n            var succeeded = void 0;\n\n            try {\n                succeeded = document.execCommand(this.action);\n            } catch (err) {\n                succeeded = false;\n            }\n\n            this.handleResult(succeeded);\n        }\n\n        /**\n         * Fires an event based on the copy operation result.\n         * @param {Boolean} succeeded\n         */\n\n    }, {\n        key: 'handleResult',\n        value: function handleResult(succeeded) {\n            this.emitter.emit(succeeded ? 'success' : 'error', {\n                action: this.action,\n                text: this.selectedText,\n                trigger: this.trigger,\n                clearSelection: this.clearSelection.bind(this)\n            });\n        }\n\n        /**\n         * Moves focus away from `target` and back to the trigger, removes current selection.\n         */\n\n    }, {\n        key: 'clearSelection',\n        value: function clearSelection() {\n            if (this.trigger) {\n                this.trigger.focus();\n            }\n            document.activeElement.blur();\n            window.getSelection().removeAllRanges();\n        }\n\n        /**\n         * Sets the `action` to be performed which can be either 'copy' or 'cut'.\n         * @param {String} action\n         */\n\n    }, {\n        key: 'destroy',\n\n\n        /**\n         * Destroy lifecycle.\n         */\n        value: function destroy() {\n            this.removeFake();\n        }\n    }, {\n        key: 'action',\n        set: function set() {\n            var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';\n\n            this._action = action;\n\n            if (this._action !== 'copy' && this._action !== 'cut') {\n                throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n            }\n        }\n\n        /**\n         * Gets the `action` property.\n         * @return {String}\n         */\n        ,\n        get: function get() {\n            return this._action;\n        }\n\n        /**\n         * Sets the `target` property using an element\n         * that will be have its content copied.\n         * @param {Element} target\n         */\n\n    }, {\n        key: 'target',\n        set: function set(target) {\n            if (target !== undefined) {\n                if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {\n                    if (this.action === 'copy' && target.hasAttribute('disabled')) {\n                        throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n                    }\n\n                    if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n                        throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n                    }\n\n                    this._target = target;\n                } else {\n                    throw new Error('Invalid \"target\" value, use a valid Element');\n                }\n            }\n        }\n\n        /**\n         * Gets the `target` property.\n         * @return {String|HTMLElement}\n         */\n        ,\n        get: function get() {\n            return this._target;\n        }\n    }]);\n\n    return ClipboardAction;\n}();\n\n/* harmony default export */ var clipboard_action = (clipboard_action_ClipboardAction);\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(1);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(2);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n\n// CONCATENATED MODULE: ./src/clipboard.js\nvar clipboard_typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar clipboard_createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\n\n\n\n\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\nvar clipboard_Clipboard = function (_Emitter) {\n    _inherits(Clipboard, _Emitter);\n\n    /**\n     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n     * @param {Object} options\n     */\n    function Clipboard(trigger, options) {\n        clipboard_classCallCheck(this, Clipboard);\n\n        var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));\n\n        _this.resolveOptions(options);\n        _this.listenClick(trigger);\n        return _this;\n    }\n\n    /**\n     * Defines if attributes would be resolved using internal setter functions\n     * or custom functions that were passed in the constructor.\n     * @param {Object} options\n     */\n\n\n    clipboard_createClass(Clipboard, [{\n        key: 'resolveOptions',\n        value: function resolveOptions() {\n            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n            this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n            this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n            this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n            this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n        }\n\n        /**\n         * Adds a click event listener to the passed trigger.\n         * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n         */\n\n    }, {\n        key: 'listenClick',\n        value: function listenClick(trigger) {\n            var _this2 = this;\n\n            this.listener = listen_default()(trigger, 'click', function (e) {\n                return _this2.onClick(e);\n            });\n        }\n\n        /**\n         * Defines a new `ClipboardAction` on each click event.\n         * @param {Event} e\n         */\n\n    }, {\n        key: 'onClick',\n        value: function onClick(e) {\n            var trigger = e.delegateTarget || e.currentTarget;\n\n            if (this.clipboardAction) {\n                this.clipboardAction = null;\n            }\n\n            this.clipboardAction = new clipboard_action({\n                action: this.action(trigger),\n                target: this.target(trigger),\n                text: this.text(trigger),\n                container: this.container,\n                trigger: trigger,\n                emitter: this\n            });\n        }\n\n        /**\n         * Default `action` lookup function.\n         * @param {Element} trigger\n         */\n\n    }, {\n        key: 'defaultAction',\n        value: function defaultAction(trigger) {\n            return getAttributeValue('action', trigger);\n        }\n\n        /**\n         * Default `target` lookup function.\n         * @param {Element} trigger\n         */\n\n    }, {\n        key: 'defaultTarget',\n        value: function defaultTarget(trigger) {\n            var selector = getAttributeValue('target', trigger);\n\n            if (selector) {\n                return document.querySelector(selector);\n            }\n        }\n\n        /**\n         * Returns the support of the given action, or all actions if no action is\n         * given.\n         * @param {String} [action]\n         */\n\n    }, {\n        key: 'defaultText',\n\n\n        /**\n         * Default `text` lookup function.\n         * @param {Element} trigger\n         */\n        value: function defaultText(trigger) {\n            return getAttributeValue('text', trigger);\n        }\n\n        /**\n         * Destroy lifecycle.\n         */\n\n    }, {\n        key: 'destroy',\n        value: function destroy() {\n            this.listener.destroy();\n\n            if (this.clipboardAction) {\n                this.clipboardAction.destroy();\n                this.clipboardAction = null;\n            }\n        }\n    }], [{\n        key: 'isSupported',\n        value: function isSupported() {\n            var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n\n            var actions = typeof action === 'string' ? [action] : action;\n            var support = !!document.queryCommandSupported;\n\n            actions.forEach(function (action) {\n                support = support && !!document.queryCommandSupported(action);\n            });\n\n            return support;\n        }\n    }]);\n\n    return Clipboard;\n}(tiny_emitter_default.a);\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\n\nfunction getAttributeValue(suffix, element) {\n    var attribute = 'data-clipboard-' + suffix;\n\n    if (!element.hasAttribute(attribute)) {\n        return;\n    }\n\n    return element.getAttribute(attribute);\n}\n\n/* harmony default export */ var clipboard = __webpack_exports__[\"default\"] = (clipboard_Clipboard);\n\n/***/ })\n/******/ ])[\"default\"];\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\nimport { NEVER, Subject, defer, merge } from \"rxjs\"\nimport {\n  delay,\n  filter,\n  map,\n  mergeWith,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n  at,\n  getElement,\n  requestJSON,\n  setToggle,\n  watchDocument,\n  watchKeyboard,\n  watchLocation,\n  watchLocationTarget,\n  watchMedia,\n  watchPrint,\n  watchViewport\n} from \"./browser\"\nimport {\n  getComponentElement,\n  getComponentElements,\n  mountContent,\n  mountDialog,\n  mountHeader,\n  mountHeaderTitle,\n  mountSearch,\n  mountSidebar,\n  mountSource,\n  mountTableOfContents,\n  mountTabs,\n  watchHeader,\n  watchMain\n} from \"./components\"\nimport {\n  SearchIndex,\n  setupClipboardJS,\n  setupInstantLoading,\n  setupVersionSelector\n} from \"./integrations\"\nimport {\n  patchIndeterminate,\n  patchScrollfix,\n  patchScrolllock\n} from \"./patches\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$   = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$   = watchMedia(\"(min-width: 960px)\")\nconst screen$   = watchMedia(\"(min-width: 1220px)\")\nconst print$    = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n  ? __search?.index || requestJSON<SearchIndex>(\n    `${config.base}/search/search_index.json`\n  )\n  : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject<string>()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n  setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n  setupVersionSelector()\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n  .pipe(\n    delay(125)\n  )\n    .subscribe(() => {\n      setToggle(\"drawer\", false)\n      setToggle(\"search\", false)\n    })\n\n/* Set up global keyboard handlers */\nkeyboard$\n  .pipe(\n    filter(({ mode }) => mode === \"global\")\n  )\n    .subscribe(key => {\n      switch (key.type) {\n\n        /* Go to previous page */\n        case \"p\":\n        case \",\":\n          const prev = getElement(\"[href][rel=prev]\")\n          if (typeof prev !== \"undefined\")\n            prev.click()\n          break\n\n        /* Go to next page */\n        case \"n\":\n        case \".\":\n          const next = getElement(\"[href][rel=next]\")\n          if (typeof next !== \"undefined\")\n            next.click()\n          break\n      }\n    })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n  .pipe(\n    map(() => getComponentElement(\"main\")),\n    switchMap(el => watchMain(el, { viewport$, header$ })),\n    shareReplay(1)\n  )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n  /* Dialog */\n  ...getComponentElements(\"dialog\")\n    .map(el => mountDialog(el, { alert$ })),\n\n  /* Header */\n  ...getComponentElements(\"header\")\n    .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n  /* Search */\n  ...getComponentElements(\"search\")\n    .map(el => mountSearch(el, { index$, keyboard$ })),\n\n  /* Repository information */\n  ...getComponentElements(\"source\")\n    .map(el => mountSource(el)),\n\n  /* Navigation tabs */\n  ...getComponentElements(\"tabs\")\n    .map(el => mountTabs(el, { viewport$, header$ })),\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n  /* Content */\n  ...getComponentElements(\"content\")\n    .map(el => mountContent(el, { target$, viewport$, print$ })),\n\n  /* Header title */\n  ...getComponentElements(\"header-title\")\n    .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n  /* Sidebar */\n  ...getComponentElements(\"sidebar\")\n    .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n      ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n      : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n    ),\n\n  /* Table of contents */\n  ...getComponentElements(\"toc\")\n    .map(el => mountTableOfContents(el, { viewport$, header$ })),\n))\n\n/* Set up component observables */\nconst component$ = document$\n  .pipe(\n    switchMap(() => content$),\n    mergeWith(control$),\n    shareReplay(1)\n  )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$  = document$          /* Document observable */\nwindow.location$  = location$          /* Location subject */\nwindow.target$    = target$            /* Location target observable */\nwindow.keyboard$  = keyboard$          /* Keyboard observable */\nwindow.viewport$  = viewport$          /* Viewport observable */\nwindow.tablet$    = tablet$            /* Tablet observable */\nwindow.screen$    = screen$            /* Screen observable */\nwindow.print$     = print$             /* Print mode observable */\nwindow.alert$     = alert$             /* Alert subject */\nwindow.component$ = component$         /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n    extendStatics = Object.setPrototypeOf ||\r\n        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n        function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n    return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n    if (typeof b !== \"function\" && b !== null)\r\n        throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n    extendStatics(d, b);\r\n    function __() { this.constructor = d; }\r\n    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n    __assign = Object.assign || function __assign(t) {\r\n        for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n            s = arguments[i];\r\n            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n        }\r\n        return t;\r\n    }\r\n    return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n    var t = {};\r\n    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n        t[p] = s[p];\r\n    if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n                t[p[i]] = s[p[i]];\r\n        }\r\n    return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n    if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n    return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n    return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n    if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n    return new (P || (P = Promise))(function (resolve, reject) {\r\n        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n        function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n        step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n    });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n    return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n    function verb(n) { return function (v) { return step([n, v]); }; }\r\n    function step(op) {\r\n        if (f) throw new TypeError(\"Generator is already executing.\");\r\n        while (_) try {\r\n            if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n            if (y = 0, t) op = [op[0] & 2, t.value];\r\n            switch (op[0]) {\r\n                case 0: case 1: t = op; break;\r\n                case 4: _.label++; return { value: op[1], done: false };\r\n                case 5: _.label++; y = op[1]; op = [0]; continue;\r\n                case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n                default:\r\n                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n                    if (t[2]) _.ops.pop();\r\n                    _.trys.pop(); continue;\r\n            }\r\n            op = body.call(thisArg, _);\r\n        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n    }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n    if (k2 === undefined) k2 = k;\r\n    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n    if (k2 === undefined) k2 = k;\r\n    o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n    for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n    var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n    if (m) return m.call(o);\r\n    if (o && typeof o.length === \"number\") return {\r\n        next: function () {\r\n            if (o && i >= o.length) o = void 0;\r\n            return { value: o && o[i++], done: !o };\r\n        }\r\n    };\r\n    throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n    var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n    if (!m) return o;\r\n    var i = m.call(o), r, ar = [], e;\r\n    try {\r\n        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n    }\r\n    catch (error) { e = { error: error }; }\r\n    finally {\r\n        try {\r\n            if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n        }\r\n        finally { if (e) throw e.error; }\r\n    }\r\n    return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n    for (var ar = [], i = 0; i < arguments.length; i++)\r\n        ar = ar.concat(__read(arguments[i]));\r\n    return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n    for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n            r[k] = a[j];\r\n    return r;\r\n}\r\n\r\nexport function __spreadArray(to, from) {\r\n    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)\r\n        to[j] = from[i];\r\n    return to;\r\n}\r\n\r\nexport function __await(v) {\r\n    return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n    return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n    function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n    function fulfill(value) { resume(\"next\", value); }\r\n    function reject(value) { resume(\"throw\", value); }\r\n    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n    var i, p;\r\n    return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n    function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var m = o[Symbol.asyncIterator], i;\r\n    return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n    if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n    return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n    Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n    o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n    if (mod && mod.__esModule) return mod;\r\n    var result = {};\r\n    if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n    __setModuleDefault(result, mod);\r\n    return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n    return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, privateMap) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to get private field on non-instance\");\r\n    }\r\n    return privateMap.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, privateMap, value) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to set private field on non-instance\");\r\n    }\r\n    privateMap.set(receiver, value);\r\n    return value;\r\n}\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ReplaySubject, Subject, fromEvent } from \"rxjs\"\nimport { mapTo } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject<Document> {\n  const document$ = new ReplaySubject<Document>()\n  fromEvent(document, \"DOMContentLoaded\")\n    .pipe(\n      mapTo(document)\n    )\n      .subscribe(document$)\n\n  /* Return document */\n  return document$\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getElement<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T | undefined\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T | undefined {\n  return node.querySelector<T>(selector) || undefined\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T {\n  const el = getElement<T>(selector, node)\n  if (typeof el === \"undefined\")\n    throw new ReferenceError(\n      `Missing element: expected \"${selector}\" to be present`\n    )\n  return el\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n  return document.activeElement instanceof HTMLElement\n    ? document.activeElement\n    : undefined\n}\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T[]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T[] {\n  return Array.from(node.querySelectorAll<T>(selector))\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Create an element\n *\n * @template T - Tag name type\n *\n * @param tagName - Tag name\n *\n * @returns Element\n */\nexport function createElement<T extends keyof HTMLElementTagNameMap>(\n  tagName: T\n): HTMLElementTagNameMap[T] {\n  return document.createElement(tagName)\n}\n\n/**\n * Replace an element with the given list of nodes\n *\n * @param el - Element\n * @param nodes - Replacement nodes\n */\nexport function replaceElement(\n  el: HTMLElement, ...nodes: Node[]\n): void {\n  el.replaceWith(...nodes)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element focus\n *\n * @param el - Element\n * @param value - Whether the element should be focused\n */\nexport function setElementFocus(\n  el: HTMLElement, value = true\n): void {\n  if (value)\n    el.focus()\n  else\n    el.blur()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n  el: HTMLElement\n): Observable<boolean> {\n  return merge(\n    fromEvent<FocusEvent>(el, \"focus\"),\n    fromEvent<FocusEvent>(el, \"blur\")\n  )\n    .pipe(\n      map(({ type }) => type === \"focus\"),\n      startWith(el === getActiveElement())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n  width: number                        /* Element width */\n  height: number                       /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject<ResizeObserverEntry>()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n  new ResizeObserver(entries => {\n    for (const entry of entries)\n      entry$.next(entry)\n  })\n))\n  .pipe(\n    switchMap(resize => NEVER.pipe(startWith(resize))\n      .pipe(\n        finalize(() => resize.disconnect())\n      )\n    ),\n    shareReplay(1)\n  )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.offsetWidth,\n    height: el.offsetHeight\n  }\n}\n\n/**\n * Retrieve element content size, i.e. including overflowing content\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementContentSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.scrollWidth,\n    height: el.scrollHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n  el: HTMLElement\n): Observable<ElementSize> {\n  return observer$\n    .pipe(\n      tap(observer => observer.observe(el)),\n      switchMap(observer => entry$\n        .pipe(\n          filter(({ target }) => target === el),\n          finalize(() => observer.unobserve(el)),\n          map(({ contentRect }) => ({\n            width:  contentRect.width,\n            height: contentRect.height\n          }))\n        )\n      ),\n      startWith(getElementSize(el))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  map,\n  startWith\n} from \"rxjs/operators\"\n\nimport {\n  getElementContentSize,\n  getElementSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(el: HTMLElement): ElementOffset {\n  return {\n    x: el.scrollLeft,\n    y: el.scrollTop\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n  el: HTMLElement\n): Observable<ElementOffset> {\n  return merge(\n    fromEvent(el, \"scroll\"),\n    fromEvent(window, \"resize\")\n  )\n    .pipe(\n      map(() => getElementOffset(el)),\n      startWith(getElementOffset(el))\n    )\n}\n\n/**\n * Watch element threshold\n *\n * This function returns an observable which emits whether the bottom scroll\n * offset of an elements is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element threshold observable\n */\nexport function watchElementThreshold(\n  el: HTMLElement, threshold = 16\n): Observable<boolean> {\n  return watchElementOffset(el)\n    .pipe(\n      map(({ y }) => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return y >= (\n          content.height - visible.height - threshold\n        )\n      }),\n      distinctUntilChanged()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element text selection\n *\n * @param el - Element\n */\nexport function setElementSelection(\n  el: HTMLElement\n): void {\n  if (el instanceof HTMLInputElement)\n    el.select()\n  else\n    throw new Error(\"Not implemented\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getElementOrThrow } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n  | \"drawer\"                           /* Toggle for drawer */\n  | \"search\"                           /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record<Toggle, HTMLInputElement> = {\n  drawer: getElementOrThrow(\"[data-md-toggle=drawer]\"),\n  search: getElementOrThrow(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n  return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n  if (toggles[name].checked !== value)\n    toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable<boolean> {\n  const el = toggles[name]\n  return fromEvent(el, \"change\")\n    .pipe(\n      map(() => el.checked),\n      startWith(el.checked)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { filter, map, share } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n  | \"global\"                           /* Global */\n  | \"search\"                           /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n  mode: KeyboardMode                   /* Keyboard mode */\n  type: string                         /* Key type */\n  claim(): void                        /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(el: HTMLElement): boolean {\n  switch (el.tagName) {\n\n    /* Form elements */\n    case \"INPUT\":\n    case \"SELECT\":\n    case \"TEXTAREA\":\n      return true\n\n    /* Everything else */\n    default:\n      return el.isContentEditable\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable<Keyboard> {\n  return fromEvent<KeyboardEvent>(window, \"keydown\")\n    .pipe(\n      filter(ev => !(ev.metaKey || ev.ctrlKey)),\n      map(ev => ({\n        mode: getToggle(\"search\") ? \"search\" : \"global\",\n        type: ev.key,\n        claim() {\n          ev.preventDefault()\n          ev.stopPropagation()\n        }\n      } as Keyboard)),\n      filter(({ mode }) => {\n        if (mode === \"global\") {\n          const active = getActiveElement()\n          if (typeof active !== \"undefined\")\n            return !isSusceptibleToKeyboard(active)\n        }\n        return true\n      }),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n  return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n  location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject<URL> {\n  return new Subject<URL>()\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport { filter, map, share, startWith, switchMap } from \"rxjs/operators\"\n\nimport { createElement, getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n  return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n  const el = createElement(\"a\")\n  el.href = hash\n  el.addEventListener(\"click\", ev => ev.stopPropagation())\n  el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable<string> {\n  return fromEvent<HashChangeEvent>(window, \"hashchange\")\n    .pipe(\n      map(getLocationHash),\n      startWith(getLocationHash()),\n      filter(hash => hash.length > 0),\n      share()\n    )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable<HTMLElement> {\n  return watchLocationHash()\n    .pipe(\n      switchMap(id => of(getElement(`[id=\"${id}\"]`)!))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  filter,\n  map,\n  mapTo,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable<boolean> {\n  const media = matchMedia(query)\n  return fromEvent<MediaQueryListEvent>(media, \"change\")\n    .pipe(\n      map(ev => ev.matches),\n      startWith(media.matches)\n    )\n}\n\n/**\n * Watch print mode, cross-browser\n *\n * @returns Print mode observable\n */\nexport function watchPrint(): Observable<void> {\n  return merge(\n    watchMedia(\"print\").pipe(filter(Boolean)),  /* Webkit */\n    fromEvent(window, \"beforeprint\")            /* IE, FF */\n  )\n    .pipe(\n      mapTo(undefined)\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at<T>(\n  query$: Observable<boolean>, factory: () => Observable<T>\n): Observable<T> {\n  return query$\n    .pipe(\n      switchMap(active => active ? factory() : NEVER)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, from } from \"rxjs\"\nimport {\n  filter,\n  map,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n  url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable<Response> {\n  return from(fetch(url.toString(), options))\n    .pipe(\n      filter(res => res.status === 200),\n    )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON<T>(\n  url: URL | string, options?: RequestInit\n): Observable<T> {\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.json()),\n      shareReplay(1)\n    )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n  url: URL | string, options?: RequestInit\n): Observable<Document> {\n  const dom = new DOMParser()\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/xml\")),\n      shareReplay(1)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n  return {\n    x: Math.max(0, pageXOffset),\n    y: Math.max(0, pageYOffset)\n  }\n}\n\n/**\n * Set viewport offset\n *\n * @param offset - Viewport offset\n */\nexport function setViewportOffset(\n  { x, y }: Partial<ViewportOffset>\n): void {\n  window.scrollTo(x || 0, y || 0)\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable<ViewportOffset> {\n  return merge(\n    fromEvent(window, \"scroll\", { passive: true }),\n    fromEvent(window, \"resize\", { passive: true })\n  )\n    .pipe(\n      map(getViewportOffset),\n      startWith(getViewportOffset())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n  width: number                        /* Viewport width */\n  height: number                       /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n  return {\n    width:  innerWidth,\n    height: innerHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable<ViewportSize> {\n  return fromEvent(window, \"resize\", { passive: true })\n    .pipe(\n      map(getViewportSize),\n      startWith(getViewportSize())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, combineLatest } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  map,\n  shareReplay\n} from \"rxjs/operators\"\n\nimport { Header } from \"~/components\"\n\nimport {\n  ViewportOffset,\n  watchViewportOffset\n} from \"../offset\"\nimport {\n  ViewportSize,\n  watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n  offset: ViewportOffset               /* Viewport offset */\n  size: ViewportSize                   /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch at options\n */\ninterface WatchAtOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable<Viewport> {\n  return combineLatest([\n    watchViewportOffset(),\n    watchViewportSize()\n  ])\n    .pipe(\n      map(([offset, size]) => ({ offset, size })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n  el: HTMLElement, { viewport$, header$ }: WatchAtOptions\n): Observable<Viewport> {\n  const size$ = viewport$\n    .pipe(\n      distinctUntilKeyChanged(\"size\")\n    )\n\n  /* Compute element offset */\n  const offset$ = combineLatest([size$, header$])\n    .pipe(\n      map((): ViewportOffset => ({\n        x: el.offsetLeft,\n        y: el.offsetTop\n      }))\n    )\n\n  /* Compute relative viewport, return hot observable */\n  return combineLatest([header$, viewport$, offset$])\n    .pipe(\n      map(([{ height }, { offset, size }, { x, y }]) => ({\n        offset: {\n          x: offset.x - x,\n          y: offset.y - y + height\n        },\n        size\n      }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, fromEvent } from \"rxjs\"\nimport {\n  map,\n  share,\n  switchMapTo,\n  tap,\n  throttle\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n  type: unknown                        /* Message type */\n  data?: unknown                       /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n  T extends WorkerMessage\n> {\n  tx$: Subject<T>                      /* Message transmission subject */\n  rx$: Observable<T>                   /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions<T extends WorkerMessage> {\n  tx$: Observable<T>                   /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker<T extends WorkerMessage>(\n  worker: Worker, { tx$ }: WatchOptions<T>\n): Observable<T> {\n\n  /* Intercept messages from worker-like objects */\n  const rx$ = fromEvent<MessageEvent>(worker, \"message\")\n    .pipe(\n      map(({ data }) => data as T)\n    )\n\n  /* Send and receive messages, return hot observable */\n  return tx$\n    .pipe(\n      throttle(() => rx$, { leading: true, trailing: true }),\n      tap(message => worker.postMessage(message)),\n      switchMapTo(rx$),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n  | \"header.autohide\"                  /* Hide header */\n  | \"navigation.expand\"                /* Automatic expansion */\n  | \"navigation.instant\"               /* Instant loading */\n  | \"navigation.sections\"              /* Sections navigation */\n  | \"navigation.tabs\"                  /* Tabs navigation */\n  | \"toc.integrate\"                    /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n  | \"clipboard.copy\"                   /* Copy to clipboard */\n  | \"clipboard.copied\"                 /* Copied to clipboard */\n  | \"search.config.lang\"               /* Search language */\n  | \"search.config.pipeline\"           /* Search pipeline */\n  | \"search.config.separator\"          /* Search separator */\n  | \"search.placeholder\"               /* Search */\n  | \"search.result.placeholder\"        /* Type to start searching */\n  | \"search.result.none\"               /* No matching documents */\n  | \"search.result.one\"                /* 1 matching document */\n  | \"search.result.other\"              /* # matching documents */\n  | \"search.result.more.one\"           /* 1 more on this page */\n  | \"search.result.more.other\"         /* # more on this page */\n  | \"search.result.term.missing\"       /* Missing */\n\n/**\n * Translations\n */\nexport type Translations = Record<Translation, string>\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n  provider: \"mike\"                     /* Version provider */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n  base: string                         /* Base URL */\n  features: Flag[]                     /* Feature flags */\n  translations: Translations           /* Translations */\n  search: string                       /* Search worker URL */\n  version?: Versioning                 /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElementOrThrow(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = new URL(config.base, getLocation())\n  .toString()\n  .replace(/\\/$/, \"\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n  return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n  return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n  key: Translation, value?: string | number\n): string {\n  return typeof value !== \"undefined\"\n    ? config.translations[key].replace(\"#\", value.toString())\n    : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component\n */\nexport type ComponentType =\n  | \"announce\"                         /* Announcement bar */\n  | \"container\"                        /* Container */\n  | \"content\"                          /* Content */\n  | \"dialog\"                           /* Dialog */\n  | \"header\"                           /* Header */\n  | \"header-title\"                     /* Header title */\n  | \"header-topic\"                     /* Header topic */\n  | \"main\"                             /* Main area */\n  | \"search\"                           /* Search */\n  | \"search-query\"                     /* Search input */\n  | \"search-result\"                    /* Search results */\n  | \"sidebar\"                          /* Sidebar */\n  | \"skip\"                             /* Skip link */\n  | \"source\"                           /* Repository information */\n  | \"tabs\"                             /* Navigation tabs */\n  | \"toc\"                              /* Table of contents */\n\n/**\n * A component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n  T extends {} = {},\n  U extends HTMLElement = HTMLElement\n> =\n  T & {\n    ref: U                             /* Component reference */\n  }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n  \"announce\": HTMLElement              /* Announcement bar */\n  \"container\": HTMLElement             /* Container */\n  \"content\": HTMLElement               /* Content */\n  \"dialog\": HTMLElement                /* Dialog */\n  \"header\": HTMLElement                /* Header */\n  \"header-title\": HTMLElement          /* Header title */\n  \"header-topic\": HTMLElement          /* Header topic */\n  \"main\": HTMLElement                  /* Main area */\n  \"search\": HTMLElement                /* Search */\n  \"search-query\": HTMLInputElement     /* Search input */\n  \"search-result\": HTMLElement         /* Search results */\n  \"sidebar\": HTMLElement               /* Sidebar */\n  \"skip\": HTMLAnchorElement            /* Skip link */\n  \"source\": HTMLAnchorElement          /* Repository information */\n  \"tabs\": HTMLElement                  /* Navigation tabs */\n  \"toc\": HTMLElement                   /* Table of contents */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n  return getElementOrThrow(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n  return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  switchMap,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetFocusable, setFocusable } from \"~/actions\"\nimport {\n  Viewport,\n  getElementContentSize,\n  getElementSize,\n  getElements,\n  watchMedia\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n  scroll: boolean                      /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global index for Clipboard.js integration\n */\nlet index = 0\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n  el: HTMLElement, { viewport$ }: WatchOptions\n): Observable<CodeBlock> {\n  const container$ = of(el)\n    .pipe(\n      switchMap(child => {\n        const container = child.closest(\"[data-tabs]\")\n        if (container instanceof HTMLElement) {\n          return merge(\n            ...getElements(\"input\", container)\n              .map(input => fromEvent(input, \"change\"))\n          )\n        }\n        return NEVER\n      })\n    )\n\n  /* Check overflow on resize and tab change */\n  return merge(\n    viewport$.pipe(distinctUntilKeyChanged(\"size\")),\n    container$\n  )\n    .pipe(\n      map(() => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return {\n          scroll: content.width > visible.width\n        }\n      }),\n      distinctUntilKeyChanged(\"scroll\")\n    )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block component observable\n */\nexport function mountCodeBlock(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<CodeBlock>> {\n  const internal$ = new Subject<CodeBlock>()\n  internal$\n    .pipe(\n      withLatestFrom(watchMedia(\"(hover)\"))\n    )\n      .subscribe(([{ scroll }, hover]) => {\n        if (scroll && hover)\n          setFocusable(el)\n        else\n          resetFocusable(el)\n      })\n\n  /* Render button for Clipboard.js integration */\n  if (ClipboardJS.isSupported()) {\n    const parent = el.closest(\"pre\")!\n    parent.id = `__code_${index++}`\n    parent.insertBefore(\n      renderClipboardButton(parent.id),\n      el\n    )\n  }\n\n  /* Create and return component */\n  return watchCodeBlock(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set focusable property\n *\n * @param el - Element\n * @param value - Tabindex value\n */\nexport function setFocusable(\n  el: HTMLElement, value = 0\n): void {\n  el.setAttribute(\"tabindex\", value.toString())\n}\n\n/**\n * Reset focusable property\n *\n * @param el - Element\n */\nexport function resetFocusable(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"tabindex\")\n}\n\n/**\n * Set scroll lock\n *\n * @param el - Scrollable element\n * @param value - Vertical offset\n */\nexport function setScrollLock(\n  el: HTMLElement, value: number\n): void {\n  el.setAttribute(\"data-md-state\", \"lock\")\n  el.style.top = `-${value}px`\n}\n\n/**\n * Reset scroll lock\n *\n * @param el - Scrollable element\n */\nexport function resetScrollLock(\n  el: HTMLElement\n): void {\n  const value = -1 * parseInt(el.style.top, 10)\n  el.removeAttribute(\"data-md-state\")\n  el.style.top = \"\"\n  if (value)\n    window.scrollTo(0, value)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set anchor state\n *\n * @param el - Anchor element\n * @param state - Anchor state\n */\nexport function setAnchorState(\n  el: HTMLElement, state: \"blur\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset anchor state\n *\n * @param el - Anchor element\n */\nexport function resetAnchorState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set anchor active\n *\n * @param el - Anchor element\n * @param value - Whether the anchor is active\n */\nexport function setAnchorActive(\n  el: HTMLElement, value: boolean\n): void {\n  el.classList.toggle(\"md-nav__link--active\", value)\n}\n\n/**\n * Reset anchor active\n *\n * @param el - Anchor element\n */\nexport function resetAnchorActive(\n  el: HTMLElement\n): void {\n  el.classList.remove(\"md-nav__link--active\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set dialog message\n *\n * @param el - Dialog element\n * @param value - Dialog message\n */\nexport function setDialogMessage(\n  el: HTMLElement, value: string\n): void {\n  el.firstElementChild!.innerHTML = value\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set dialog state\n *\n * @param el - Dialog element\n * @param state - Dialog state\n */\nexport function setDialogState(\n  el: HTMLElement, state: \"open\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset dialog state\n *\n * @param el - Dialog element\n */\nexport function resetDialogState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header state\n *\n * @param el - Header element\n * @param state - Header state\n */\nexport function setHeaderState(\n  el: HTMLElement, state: \"shadow\" | \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header state\n *\n * @param el - Header element\n */\nexport function resetHeaderState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header title state\n *\n * @param el - Header title element\n * @param state - Header title state\n */\nexport function setHeaderTitleState(\n  el: HTMLElement, state: \"active\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header title state\n *\n * @param el - Header title element\n */\nexport function resetHeaderTitleState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set search query placeholder\n *\n * @param el - Search query element\n * @param value - Placeholder\n */\nexport function setSearchQueryPlaceholder(\n  el: HTMLInputElement, value: string\n): void {\n  el.placeholder = value\n}\n\n/**\n * Reset search query placeholder\n *\n * @param el - Search query element\n */\nexport function resetSearchQueryPlaceholder(\n  el: HTMLInputElement\n): void {\n  el.placeholder = translation(\"search.placeholder\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n  & JSXInternal.HTMLAttributes\n  & JSXInternal.SVGAttributes\n  & Record<string, any>\n\n/**\n * Child element\n */\ntype Child =\n  | HTMLElement\n  | Text\n  | string\n  | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n  /* Handle primitive types (including raw HTML) */\n  if (typeof child === \"string\" || typeof child === \"number\") {\n    el.innerHTML += child.toString()\n\n  /* Handle nodes */\n  } else if (child instanceof Node) {\n    el.appendChild(child)\n\n  /* Handle nested children */\n  } else if (Array.isArray(child)) {\n    for (const node of child)\n      appendChild(el, node)\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n  tag: string, attributes: Attributes | null, ...children: Child[]\n): HTMLElement {\n  const el = document.createElement(tag)\n\n  /* Set attributes, if any */\n  if (attributes)\n    for (const attr of Object.keys(attributes))\n      if (typeof attributes[attr] !== \"boolean\")\n        el.setAttribute(attr, attributes[attr])\n      else if (attributes[attr])\n        el.setAttribute(attr, \"\")\n\n  /* Append child nodes */\n  for (const child of children)\n    appendChild(el, child)\n\n  /* Return element */\n  return el\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n  namespace JSX {\n    type Element = HTMLElement\n    type IntrinsicElements = JSXInternal.IntrinsicElements\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n  let i = n\n  if (value.length > i) {\n    while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n    return `${value.substring(0, i)}...`\n  }\n  return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n  if (value > 999) {\n    const digits = +((value - 950) % 1000 > 99)\n    return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n  } else {\n    return value.toString()\n  }\n}\n\n/**\n * Simple hash function\n *\n * @see https://bit.ly/2wsVjJ4 - Original source\n *\n * @param value - Value to be hashed\n *\n * @returns Hash as 32bit integer\n */\nexport function hash(value: string): number {\n  let h = 0\n  for (let i = 0, len = value.length; i < len; i++) {\n    h  = ((h << 5) - h) + value.charCodeAt(i)\n    h |= 0 // Convert to 32bit integer\n  }\n  return h\n}\n\n/**\n * Add a digest to a value to ensure uniqueness\n *\n * When a single account hosts multiple sites on the same GitHub Pages domain,\n * entries would collide, because session and local storage are not unique.\n *\n * @param value - Value\n *\n * @returns Value with digest\n */\nexport function digest(value: string): string {\n  const config = configuration()\n  return `${value}[${hash(config.base)}]`\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set number of search results\n *\n * @param el - Search result metadata element\n * @param value - Number of results\n */\nexport function setSearchResultMeta(\n  el: HTMLElement, value: number\n): void {\n  switch (value) {\n\n    /* No results */\n    case 0:\n      el.textContent = translation(\"search.result.none\")\n      break\n\n    /* One result */\n    case 1:\n      el.textContent = translation(\"search.result.one\")\n      break\n\n    /* Multiple result */\n    default:\n      el.textContent = translation(\"search.result.other\", round(value))\n  }\n}\n\n/**\n * Reset number of search results\n *\n * @param el - Search result metadata element\n */\nexport function resetSearchResultMeta(\n  el: HTMLElement\n): void {\n  el.textContent = translation(\"search.result.placeholder\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Add an element to the search result list\n *\n * @param el - Search result list element\n * @param child - Search result element\n */\nexport function addToSearchResultList(\n  el: HTMLElement, child: Element\n): void {\n  el.appendChild(child)\n}\n\n/**\n * Reset search result list\n *\n * @param el - Search result list element\n */\nexport function resetSearchResultList(\n  el: HTMLElement\n): void {\n  el.innerHTML = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar offset\n *\n * @param el - Sidebar element\n * @param value - Sidebar offset\n */\nexport function setSidebarOffset(\n  el: HTMLElement, value: number\n): void {\n  el.style.top = `${value}px`\n}\n\n/**\n * Reset sidebar offset\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarOffset(\n  el: HTMLElement\n): void {\n  el.style.top = \"\"\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar height\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param value - Sidebar height\n */\nexport function setSidebarHeight(\n  el: HTMLElement, value: number\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`\n}\n\n/**\n * Reset sidebar height\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarHeight(\n  el: HTMLElement\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set repository facts\n *\n * @param el - Repository element\n * @param child - Repository facts element\n */\nexport function setSourceFacts(\n  el: HTMLElement, child: Element\n): void {\n  el.lastElementChild!.appendChild(child)\n}\n\n/**\n * Set repository state\n *\n * @param el - Repository element\n * @param state - Repository state\n */\nexport function setSourceState(\n  el: HTMLElement, state: \"done\"\n): void {\n  el.lastElementChild!.setAttribute(\"data-md-state\", state)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set tabs state\n *\n * @param el - Tabs element\n * @param state - Tabs state\n */\nexport function setTabsState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset tabs state\n *\n * @param el - Tabs element\n */\nexport function resetTabsState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n  return (\n    <button\n      class=\"md-clipboard md-icon\"\n      title={translation(\"clipboard.copy\")}\n      data-clipboard-target={`#${id} > code`}\n    ></button>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport {\n  SearchDocument,\n  SearchMetadata,\n  SearchResult\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n  TEASER = 1,                          /* Render teaser */\n  PARENT = 2                           /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n  document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n  const parent = flag & Flag.PARENT\n  const teaser = flag & Flag.TEASER\n\n  /* Render missing query terms */\n  const missing = Object.keys(document.terms)\n    .filter(key => !document.terms[key])\n    .map(key => [<del>{key}</del>, \" \"])\n    .flat()\n    .slice(0, -1)\n\n  /* Render article or section, depending on flags */\n  const url = document.location\n  return (\n    <a href={url} class=\"md-search-result__link\" tabIndex={-1}>\n      <article\n        class={[\"md-search-result__article\", ...parent\n          ? [\"md-search-result__article--document\"]\n          : []\n        ].join(\" \")}\n        data-md-score={document.score.toFixed(2)}\n      >\n        {parent > 0 && <div class=\"md-search-result__icon md-icon\"></div>}\n        <h1 class=\"md-search-result__title\">{document.title}</h1>\n        {teaser > 0 && document.text.length > 0 &&\n          <p class=\"md-search-result__teaser\">\n            {truncate(document.text, 320)}\n          </p>\n        }\n        {teaser > 0 && missing.length > 0 &&\n          <p class=\"md-search-result__terms\">\n            {translation(\"search.result.term.missing\")}: {...missing}\n          </p>\n        }\n      </article>\n    </a>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResult(\n  result: SearchResult\n): HTMLElement {\n  const threshold = result[0].score\n  const docs = [...result]\n\n  /* Find and extract parent article */\n  const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n  const [article] = docs.splice(parent, 1)\n\n  /* Determine last index above threshold */\n  let index = docs.findIndex(doc => doc.score < threshold)\n  if (index === -1)\n    index = docs.length\n\n  /* Partition sections */\n  const best = docs.slice(0, index)\n  const more = docs.slice(index)\n\n  /* Render children */\n  const children = [\n    renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n    ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n    ...more.length ? [\n      <details class=\"md-search-result__more\">\n        <summary tabIndex={-1}>\n          {more.length > 0 && more.length === 1\n            ? translation(\"search.result.more.one\")\n            : translation(\"search.result.more.other\", more.length)\n          }\n        </summary>\n        {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n      </details>\n    ] : []\n  ]\n\n  /* Render search result */\n  return (\n    <li class=\"md-search-result__item\">\n      {children}\n    </li>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n  return (\n    <ul class=\"md-source__facts\">\n      {facts.map(fact => (\n        <li class=\"md-source__fact\">{fact}</li>\n      ))}\n    </ul>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n  return (\n    <div class=\"md-typeset__scrollwrap\">\n      <div class=\"md-typeset__table\">\n        {table}\n      </div>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n  version: string                      /* Version identifier */\n  title: string                        /* Version title */\n  aliases: string[]                    /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n *\n * @returns Element\n */\nexport function renderVersionSelector(versions: Version[]): HTMLElement {\n  const config = configuration()\n\n  /* Determine active version */\n  const [, current] = config.base.match(/([^/]+)\\/?$/)!\n  const active =\n    versions.find(({ version, aliases }) => (\n      version === current || aliases.includes(current)\n    )) || versions[0]\n\n  /* Render version selector */\n  return (\n    <div class=\"md-version\">\n      <span class=\"md-version__current\">\n        {active.version}\n      </span>\n      <ul class=\"md-version__list\">\n        {versions.map(version => (\n          <li class=\"md-version__item\">\n            <a\n              class=\"md-version__link\"\n              href={`${new URL(version.version, config.base)}`}\n            >\n              {version.title}\n            </a>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject } from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  mapTo,\n  mergeWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n  el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable<Details> {\n  return target$\n    .pipe(\n      map(target => target.closest(\"details:not([open])\")!),\n      filter(details => el === details),\n      mergeWith(print$),\n      mapTo(el)\n    )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n  el: HTMLDetailsElement, options: MountOptions\n): Observable<Component<Details>> {\n  const internal$ = new Subject<Details>()\n  internal$.subscribe(() => {\n    el.setAttribute(\"open\", \"\")\n    el.scrollIntoView()\n  })\n\n  /* Create and return component */\n  return watchDetails(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      mapTo({ ref: el })\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { createElement, replaceElement } from \"~/browser\"\nimport { renderTable } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = createElement(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n  el: HTMLElement\n): Observable<Component<DataTable>> {\n  replaceElement(el, sentinel)\n  replaceElement(sentinel, renderTable(el))\n\n  /* Create and return component */\n  return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { Viewport, getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { CodeBlock, mountCodeBlock } from \"../code\"\nimport { Details, mountDetails } from \"../details\"\nimport { DataTable, mountDataTable } from \"../table\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n  | CodeBlock\n  | DataTable\n  | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n  el: HTMLElement, { target$, viewport$, print$ }: MountOptions\n): Observable<Component<Content>> {\n  return merge(\n\n    /* Code blocks */\n    ...getElements(\"pre > code\", el)\n      .map(child => mountCodeBlock(child, { viewport$ })),\n\n    /* Data tables */\n    ...getElements(\"table:not([class])\", el)\n      .map(child => mountDataTable(child)),\n\n    /* Details */\n    ...getElements(\"details\", el)\n      .map(child => mountDetails(child, { target$, print$ }))\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetDialogState,\n  setDialogMessage,\n  setDialogState\n} from \"~/actions\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n  message: string                      /* Dialog message */\n  open: boolean                        /* Dialog is visible */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n  _el: HTMLElement, { alert$ }: WatchOptions\n): Observable<Dialog> {\n  return alert$\n    .pipe(\n      switchMap(message => merge(\n        of(true),\n        of(false).pipe(delay(2000))\n      )\n        .pipe(\n          map(open => ({ message, open }))\n        )\n      )\n    )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right cornerwhen a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Dialog>> {\n  const internal$ = new Subject<Dialog>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ message, open }) => {\n        setDialogMessage(el, message)\n        if (open)\n          setDialogState(el, \"open\")\n        else\n          resetDialogState(el)\n      })\n\n  /* Create and return component */\n  return watchDialog(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  combineLatestWith,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  observeOn,\n  shareReplay,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { feature } from \"~/_\"\nimport { resetHeaderState, setHeaderState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n  height: number                       /* Header visible height */\n  sticky: boolean                      /* Header stickyness */\n  hidden: boolean                      /* User scrolled past threshold */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable<boolean> {\n  if (!feature(\"header.autohide\"))\n    return of(false)\n\n  /* Compute direction and turning point */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => [a < b, b] as const),\n      distinctUntilKeyChanged(0)\n    )\n\n  /* Compute whether header should be hidden */\n  const hidden$ = combineLatest([viewport$, direction$])\n    .pipe(\n      filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n      map(([, [direction]]) => direction),\n      distinctUntilChanged()\n    )\n\n  /* Compute threshold for autohiding */\n  const search$ = watchToggle(\"search\")\n  return combineLatest([viewport$, search$])\n    .pipe(\n      map(([{ offset }, search]) => offset.y > 400 && !search),\n      distinctUntilChanged(),\n      switchMap(active => active ? hidden$ : of(false)),\n      startWith(false)\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n  el: HTMLElement, options: WatchOptions\n): Observable<Header> {\n  return defer(() => {\n    const styles = getComputedStyle(el)\n    return of(\n      styles.position === \"sticky\" ||\n      styles.position === \"-webkit-sticky\"\n    )\n  })\n    .pipe(\n      combineLatestWith(watchElementSize(el), isHidden(options)),\n      map(([sticky, { height }, hidden]) => ({\n        height: sticky ? height : 0,\n        sticky,\n        hidden\n      })),\n      distinctUntilChanged((a, b) => (\n        a.sticky === b.sticky &&\n        a.height === b.height &&\n        a.hidden === b.hidden\n      )),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n  el: HTMLElement, { header$, main$ }: MountOptions\n): Observable<Component<Header>> {\n  const internal$ = new Subject<Main>()\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"active\"),\n      combineLatestWith(header$),\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(([{ active }, { hidden }]) => {\n        if (active)\n          setHeaderState(el, hidden ? \"hidden\" : \"shadow\")\n        else\n          resetHeaderState(el)\n      })\n\n  /* Connect to long-living subject and return component */\n  main$.subscribe(main => internal$.next(main))\n  return header$\n    .pipe(\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  animationFrameScheduler\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetHeaderTitleState,\n  setHeaderTitleState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n  active: boolean                      /* User scrolled past first headline */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n  el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions\n): Observable<HeaderTitle> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        const { height } = getElementSize(el)\n        return {\n          active: y >= height\n        }\n      }),\n      distinctUntilKeyChanged(\"active\")\n    )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<HeaderTitle>> {\n  const internal$ = new Subject<HeaderTitle>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ active }) => {\n        if (active)\n          setHeaderTitleState(el, \"active\")\n        else\n          resetHeaderTitleState(el)\n      })\n\n  /* Obtain headline, if any */\n  const headline = getElement<HTMLHeadingElement>(\"article h1\")\n  if (typeof headline === \"undefined\")\n    return NEVER\n\n  /* Create and return component */\n  return watchHeaderTitle(headline, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  map,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { Viewport, watchElementSize } from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n  offset: number                       /* Main area top offset */\n  height: number                       /* Main area visible height */\n  active: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Main> {\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(({ height }) => height),\n      distinctUntilChanged()\n    )\n\n  /* Compute the main area's top and bottom borders */\n  const border$ = adjust$\n    .pipe(\n      switchMap(() => watchElementSize(el)\n        .pipe(\n          map(({ height }) => ({\n            top:    el.offsetTop,\n            bottom: el.offsetTop + height\n          })),\n          distinctUntilKeyChanged(\"bottom\")\n        )\n      )\n    )\n\n  /* Compute the main area's offset, visible height and if we scrolled past */\n  return combineLatest([adjust$, border$, viewport$])\n    .pipe(\n      map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n        height = Math.max(0, height\n          - Math.max(0, top    - y,  header)\n          - Math.max(0, height + y - bottom)\n        )\n        return {\n          offset: top - header,\n          height,\n          active: top - header <= y\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.offset === b.offset &&\n        a.height === b.height &&\n        a.active === b.active\n      ))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport { Observable, Subject } from \"rxjs\"\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n  { alert$ }: SetupOptions\n): void {\n  if (ClipboardJS.isSupported()) {\n    new Observable<ClipboardJS.Event>(subscriber => {\n      new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\")\n        .on(\"success\", ev => subscriber.next(ev))\n    })\n      .subscribe(() => alert$.next(translation(\"clipboard.copied\")))\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  EMPTY,\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  catchError,\n  concatMap,\n  debounceTime,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  sample,\n  share,\n  skip,\n  skipUntil,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Viewport,\n  ViewportOffset,\n  createElement,\n  getElement,\n  getElements,\n  replaceElement,\n  request,\n  requestXML,\n  setLocation,\n  setLocationHash,\n  setViewportOffset\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n  url: URL                             /* State URL */\n  offset?: ViewportOffset              /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  document$: Subject<Document>         /* Document subject */\n  location$: Subject<URL>              /* Location subject */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns Processed URLs\n */\nfunction preprocess(urls: string[]): string[] {\n  if (urls.length < 2)\n    return urls\n\n  /* Take the first two URLs and remove everything after the last slash */\n  const [root, next] = urls\n    .sort((a, b) => a.length - b.length)\n    .map(url => url.replace(/[^/]+$/, \"\"))\n\n  /* Compute common prefix */\n  let index = 0\n  if (root === next)\n    index = root.length\n  else\n    while (root.charCodeAt(index) === next.charCodeAt(index))\n      index++\n\n  /* Replace common prefix (i.e. base) with effective base */\n  const config = configuration()\n  return urls.map(url => (\n    url.replace(root.slice(0, index), `${config.base}/`)\n  ))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n  { document$, location$, viewport$ }: SetupOptions\n): void {\n  const config = configuration()\n  if (location.protocol === \"file:\")\n    return\n\n  /* Disable automatic scroll restoration */\n  if (\"scrollRestoration\" in history) {\n    history.scrollRestoration = \"manual\"\n\n    /* Hack: ensure that reloads restore viewport offset */\n    fromEvent(window, \"beforeunload\")\n      .subscribe(() => {\n        history.scrollRestoration = \"auto\"\n      })\n  }\n\n  /* Hack: ensure absolute favicon link to omit 404s when switching */\n  const favicon = getElement<HTMLLinkElement>(\"link[rel='shortcut icon']\")\n  if (typeof favicon !== \"undefined\")\n    favicon.href = favicon.href\n\n  /* Intercept internal navigation */\n  const push$ = requestXML(`${config.base}/sitemap.xml`)\n    .pipe(\n      map(sitemap => preprocess(getElements(\"loc\", sitemap)\n        .map(node => node.textContent!)\n      )),\n      switchMap(urls => fromEvent<MouseEvent>(document.body, \"click\")\n        .pipe(\n          filter(ev => !ev.metaKey && !ev.ctrlKey),\n          switchMap(ev => {\n\n            /* Handle HTML and SVG elements */\n            if (ev.target instanceof Element) {\n              const el = ev.target.closest(\"a\")\n              if (el && !el.target && urls.includes(el.href)) {\n                ev.preventDefault()\n                return of({\n                  url: new URL(el.href)\n                })\n              }\n            }\n            return NEVER\n          })\n        )\n      ),\n      share<HistoryState>()\n    )\n\n  /* Intercept history back and forward */\n  const pop$ = fromEvent<PopStateEvent>(window, \"popstate\")\n    .pipe(\n      filter(ev => ev.state !== null),\n      map(ev => ({\n        url: new URL(location.href),\n        offset: ev.state\n      })),\n      share<HistoryState>()\n    )\n\n  /* Emit location change */\n  merge(push$, pop$)\n    .pipe(\n      distinctUntilChanged((a, b) => a.url.href === b.url.href),\n      map(({ url }) => url)\n    )\n      .subscribe(location$)\n\n  /* Fetch document via `XMLHTTPRequest` */\n  const response$ = location$\n    .pipe(\n      distinctUntilKeyChanged(\"pathname\"),\n      switchMap(url => request(url.href)\n        .pipe(\n          catchError(() => {\n            setLocation(url)\n            return NEVER\n          })\n        )\n      ),\n      share()\n    )\n\n  /* Set new location via `history.pushState` */\n  push$\n    .pipe(\n      sample(response$)\n    )\n      .subscribe(({ url }) => {\n        history.pushState({}, \"\", url.toString())\n      })\n\n  /* Parse and emit fetched document */\n  const dom = new DOMParser()\n  response$\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/html\"))\n    )\n      .subscribe(document$)\n\n  /* Emit history state change */\n  merge(push$, pop$)\n    .pipe(\n      sample(document$)\n    )\n      .subscribe(({ url, offset }) => {\n        if (url.hash && !offset)\n          setLocationHash(url.hash)\n        else\n          setViewportOffset(offset || { y: 0 })\n      })\n\n  /* Replace meta tags and components */\n  document$\n    .pipe(\n      skip(1)\n    )\n      .subscribe(replacement => {\n        for (const selector of [\n\n          /* Meta tags */\n          \"title\",\n          \"link[rel='canonical']\",\n          \"meta[name='author']\",\n          \"meta[name='description']\",\n\n          /* Components */\n          \"[data-md-component=announce]\",\n          \"[data-md-component=header-topic]\",\n          \"[data-md-component=container]\",\n          \"[data-md-component=skip]\"\n        ]) {\n          const source = getElement(selector)\n          const target = getElement(selector, replacement)\n          if (\n            typeof source !== \"undefined\" &&\n            typeof target !== \"undefined\"\n          ) {\n            replaceElement(source, target)\n          }\n        }\n      })\n\n  /* Re-evaluate scripts */\n  document$\n    .pipe(\n      skip(1),\n      map(() => getComponentElement(\"container\")),\n      switchMap(el => of(...getElements(\"script\", el))),\n      concatMap(el => {\n        const script = createElement(\"script\")\n        if (el.src) {\n          for (const name of el.getAttributeNames())\n            script.setAttribute(name, el.getAttribute(name)!)\n          replaceElement(el, script)\n\n          /* Complete when script is loaded */\n          return new Observable(observer => {\n            script.onload = () => observer.complete()\n          })\n\n        /* Complete immediately */\n        } else {\n          script.textContent = el.textContent!\n          replaceElement(el, script)\n          return EMPTY\n        }\n      })\n    )\n      .subscribe()\n\n  /* Debounce update of viewport offset */\n  viewport$\n    .pipe(\n      skipUntil(push$),\n      debounceTime(250),\n      distinctUntilKeyChanged(\"offset\")\n    )\n      .subscribe(({ offset }) => {\n        history.replaceState(offset, \"\")\n      })\n\n  /* Set viewport offset from history */\n  merge(push$, pop$)\n    .pipe(\n      bufferCount(2, 1),\n      filter(([a, b]) => a.url.pathname === b.url.pathname),\n      map(([, state]) => state)\n    )\n      .subscribe(({ offset }) => {\n        setViewportOffset(offset || { y: 0 })\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n *    that the resulting document must contain all terms, converting the query\n *    to an `AND` query (as opposed to the default `OR` behavior). While users\n *    may expect terms enclosed in quotation marks to map to span queries, i.e.\n *    for which order is important, Lunr.js doesn't support them, so the best\n *    we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n *    query or preceded by white space, or are not followed by a non-whitespace\n *    character or are at the end of the query string. Furthermore, filter\n *    unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n  return query\n    .split(/\"([^\"]+)\"/g)                            /* => 1 */\n      .map((terms, index) => index & 1\n        ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n        : terms\n      )\n      .join(\"\")\n    .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n    .trim()                                         /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ObservableInput, Subject, from } from \"rxjs\"\nimport { map, share } from \"rxjs/operators\"\n\nimport { configuration, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex, SearchIndexPipeline } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType,\n  SearchSetupMessage,\n  isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler<SearchMessage>\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex(\n  { config, docs, index }: SearchIndex\n): SearchIndex {\n\n  /* Override default language with value from translation */\n  if (config.lang.length === 1 && config.lang[0] === \"en\")\n    config.lang = [\n      translation(\"search.config.lang\")\n    ]\n\n  /* Override default separator with value from translation */\n  if (config.separator === \"[\\\\s\\\\-]+\")\n    config.separator = translation(\"search.config.separator\")\n\n  /* Set pipeline from translation */\n  const pipeline = translation(\"search.config.pipeline\")\n    .split(/\\s*,\\s*/)\n    .filter(Boolean) as SearchIndexPipeline\n\n  /* Return search index after defaulting */\n  return { config, docs, index, pipeline }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n  url: string, index: ObservableInput<SearchIndex>\n): SearchWorker {\n  const config = configuration()\n  const worker = new Worker(url)\n\n  /* Create communication channels and resolve relative links */\n  const tx$ = new Subject<SearchMessage>()\n  const rx$ = watchWorker(worker, { tx$ })\n    .pipe(\n      map(message => {\n        if (isSearchResultMessage(message)) {\n          for (const result of message.data)\n            for (const document of result)\n              document.location = `${config.base}/${document.location}`\n        }\n        return message\n      }),\n      share()\n    )\n\n  /* Set up search index */\n  from(index)\n    .pipe(\n      map<SearchIndex, SearchSetupMessage>(data => ({\n        type: SearchMessageType.SETUP,\n        data: setupSearchIndex(data)\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Return search worker */\n  return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { getElementOrThrow, requestJSON } from \"~/browser\"\nimport { Version, renderVersionSelector } from \"~/templates\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n */\nexport function setupVersionSelector(): void {\n  const config = configuration()\n  requestJSON<Version[]>(new URL(\"versions.json\", config.base))\n    .subscribe(versions => {\n      const topic = getElementOrThrow(\".md-header__topic\")\n      topic.appendChild(renderVersionSelector(versions))\n    })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  combineLatest,\n  fromEvent,\n  merge\n} from \"rxjs\"\nimport {\n  delay,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  takeLast,\n  takeUntil,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetSearchQueryPlaceholder,\n  setSearchQueryPlaceholder\n} from \"~/actions\"\nimport {\n  setElementFocus,\n  setToggle,\n  watchElementFocus\n} from \"~/browser\"\nimport {\n  SearchMessageType,\n  SearchQueryMessage,\n  SearchWorker,\n  defaultTransform\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n  value: string                        /* Query value */\n  focus: boolean                       /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n  el: HTMLInputElement\n): Observable<SearchQuery> {\n  const fn = __search?.transform || defaultTransform\n\n  /* Intercept focus and input events */\n  const focus$ = watchElementFocus(el)\n  const value$ = merge(\n    fromEvent(el, \"keyup\"),\n    fromEvent(el, \"focus\").pipe(delay(1))\n  )\n    .pipe(\n      map(() => fn(el.value)),\n      distinctUntilChanged()\n    )\n\n  /* Combine into single observable */\n  return combineLatest([value$, focus$])\n    .pipe(\n      map(([value, focus]) => ({ value, focus }))\n    )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n  el: HTMLInputElement, { tx$ }: SearchWorker\n): Observable<Component<SearchQuery, HTMLInputElement>> {\n  const internal$ = new Subject<SearchQuery>()\n\n  /* Handle value changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"value\"),\n      map(({ value }): SearchQueryMessage => ({\n        type: SearchMessageType.QUERY,\n        data: value\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Handle focus changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"focus\")\n    )\n      .subscribe(({ focus }) => {\n        if (focus) {\n          setToggle(\"search\", focus)\n          setSearchQueryPlaceholder(el, \"\")\n        } else {\n          resetSearchQueryPlaceholder(el)\n        }\n      })\n\n  /* Handle reset */\n  fromEvent(el.form!, \"reset\")\n    .pipe(\n      takeUntil(internal$.pipe(takeLast(1)))\n    )\n      .subscribe(() => setElementFocus(el))\n\n  /* Create and return component */\n  return watchSearchQuery(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  filter,\n  finalize,\n  map,\n  observeOn,\n  startWith,\n  switchMap,\n  tap,\n  withLatestFrom,\n  zipWith\n} from \"rxjs/operators\"\n\nimport {\n  addToSearchResultList,\n  resetSearchResultList,\n  resetSearchResultMeta,\n  setSearchResultMeta\n} from \"~/actions\"\nimport {\n  getElementOrThrow,\n  watchElementThreshold\n} from \"~/browser\"\nimport {\n  SearchResult as SearchResultData,\n  SearchWorker,\n  isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResult } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n  data: SearchResultData[]             /* Search result data */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  query$: Observable<SearchQuery>      /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n  el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable<Component<SearchResult>> {\n  const internal$ = new Subject<SearchResult>()\n  const boundary$ = watchElementThreshold(el.parentElement!)\n    .pipe(\n      filter(Boolean)\n    )\n\n  /* Update search result metadata */\n  const meta = getElementOrThrow(\":scope > :first-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(query$)\n    )\n      .subscribe(([{ data }, { value }]) => {\n        if (value)\n          setSearchResultMeta(meta, data.length)\n        else\n          resetSearchResultMeta(meta)\n      })\n\n  /* Update search result list */\n  const list = getElementOrThrow(\":scope > :last-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      tap(() => resetSearchResultList(list)),\n      switchMap(({ data }) => merge(\n        of(...data.slice(0, 10)),\n        of(...data.slice(10))\n          .pipe(\n            bufferCount(4),\n            zipWith(boundary$),\n            switchMap(([chunk]) => of(...chunk))\n          )\n      ))\n    )\n      .subscribe(result => {\n        addToSearchResultList(list, renderSearchResult(result))\n      })\n\n  /* Filter search result list */\n  const result$ = rx$\n    .pipe(\n      filter(isSearchResultMessage),\n      map(({ data }) => ({ data })),\n      startWith({ data: [] })\n    )\n\n  /* Create and return component */\n  return result$\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, ObservableInput, merge } from \"rxjs\"\nimport { filter, sample, take } from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Keyboard,\n  getActiveElement,\n  getElements,\n  setElementFocus,\n  setElementSelection,\n  setToggle\n} from \"~/browser\"\nimport {\n  SearchIndex,\n  isSearchQueryMessage,\n  isSearchReadyMessage,\n  setupSearchWorker\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\nimport { SearchQuery, mountSearchQuery } from \"../query\"\nimport { SearchResult, mountSearchResult } from \"../result\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n  | SearchQuery\n  | SearchResult\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  index$: ObservableInput<SearchIndex> /* Search index observable */\n  keyboard$: Observable<Keyboard>      /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n  el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable<Component<Search>> {\n  const config = configuration()\n  const worker = setupSearchWorker(config.search, index$)\n\n  /* Retrieve nested components */\n  const query  = getComponentElement(\"search-query\", el)\n  const result = getComponentElement(\"search-result\", el)\n\n  /* Re-emit query when search is ready */\n  const { tx$, rx$ } = worker\n  tx$\n    .pipe(\n      filter(isSearchQueryMessage),\n      sample(rx$.pipe(filter(isSearchReadyMessage))),\n      take(1)\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Set up search keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"search\")\n    )\n      .subscribe(key => {\n        const active = getActiveElement()\n        switch (key.type) {\n\n          /* Enter: prevent form submission */\n          case \"Enter\":\n            if (active === query)\n              key.claim()\n            break\n\n          /* Escape or Tab: close search */\n          case \"Escape\":\n          case \"Tab\":\n            setToggle(\"search\", false)\n            setElementFocus(query, false)\n            break\n\n          /* Vertical arrows: select previous or next search result */\n          case \"ArrowUp\":\n          case \"ArrowDown\":\n            if (typeof active === \"undefined\") {\n              setElementFocus(query)\n            } else {\n              const els = [query, ...getElements(\n                \":not(details) > [href], summary, details[open] [href]\",\n                result\n              )]\n              const i = Math.max(0, (\n                Math.max(0, els.indexOf(active)) + els.length + (\n                  key.type === \"ArrowUp\" ? -1 : +1\n                )\n              ) % els.length)\n              setElementFocus(els[i])\n            }\n\n            /* Prevent scrolling of page */\n            key.claim()\n            break\n\n          /* All other keys: hand to search query */\n          default:\n            if (query !== getActiveElement())\n              setElementFocus(query)\n        }\n      })\n\n  /* Set up global keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"global\"),\n    )\n      .subscribe(key => {\n        switch (key.type) {\n\n          /* Open search and select query */\n          case \"f\":\n          case \"s\":\n          case \"/\":\n            setElementFocus(query)\n            setElementSelection(query)\n            key.claim()\n            break\n        }\n      })\n\n  /* Create and return component */\n  const query$ = mountSearchQuery(query, worker)\n  return merge(\n    query$,\n    mountSearchResult(result, worker, { query$ })\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  finalize,\n  map,\n  observeOn,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport {\n  resetSidebarHeight,\n  resetSidebarOffset,\n  setSidebarHeight,\n  setSidebarOffset\n} from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n  height: number                       /* Sidebar height */\n  locked: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n  el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<Sidebar> {\n  const adjust =\n    el.parentElement!.offsetTop -\n    el.parentElement!.parentElement!.offsetTop\n\n  /* Compute the sidebar's available height and if it should be locked */\n  return combineLatest([main$, viewport$])\n    .pipe(\n      map(([{ offset, height }, { offset: { y } }]) => {\n        height = height\n          + Math.min(adjust, Math.max(0, y - offset))\n          - adjust\n        return {\n          height,\n          locked: y >= offset + adjust\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.height === b.height &&\n        a.locked === b.locked\n      ))\n    )\n}\n\n/**\n * Mount sidebar\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n  el: HTMLElement, { header$, ...options }: MountOptions\n): Observable<Component<Sidebar>> {\n  const internal$ = new Subject<Sidebar>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(header$)\n    )\n      .subscribe({\n\n        /* Update height and offset */\n        next([{ height }, { height: offset }]) {\n          setSidebarHeight(el, height)\n          setSidebarOffset(el, offset)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetSidebarOffset(el)\n          resetSidebarHeight(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchSidebar(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\nimport { round } from \"~/utilities\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n  user: string, repo?: string\n): Observable<SourceFacts> {\n  const url = typeof repo !== \"undefined\"\n    ? `https://api.github.com/repos/${user}/${repo}`\n    : `https://api.github.com/users/${user}`\n  return requestJSON<Repo & User>(url)\n    .pipe(\n      map(data => {\n\n        /* GitHub repository */\n        if (typeof repo !== \"undefined\") {\n          const { stargazers_count, forks_count }: Repo = data\n          return [\n            `${round(stargazers_count!)} Stars`,\n            `${round(forks_count!)} Forks`\n          ]\n\n        /* GitHub user/organization */\n        } else {\n          const { public_repos }: User = data\n          return [\n            `${round(public_repos!)} Repositories`\n          ]\n        }\n      }),\n      defaultIfEmpty([])\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\nimport { round } from \"~/utilities\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n  base: string, project: string\n): Observable<SourceFacts> {\n  const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n  return requestJSON<ProjectSchema>(url)\n    .pipe(\n      map(({ star_count, forks_count }) => ([\n        `${round(star_count)} Stars`,\n        `${round(forks_count)} Forks`\n      ])),\n      defaultIfEmpty([])\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts = string[]\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n  url: string\n): Observable<SourceFacts> {\n  const [type] = url.match(/(git(?:hub|lab))/i) || []\n  switch (type.toLowerCase()) {\n\n    /* GitHub repository */\n    case \"github\":\n      const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n      return fetchSourceFactsFromGitHub(user, repo)\n\n    /* GitLab repository */\n    case \"gitlab\":\n      const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n      return fetchSourceFactsFromGitLab(base, slug)\n\n    /* Everything else */\n    default:\n      return NEVER\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, Subject, defer, of } from \"rxjs\"\nimport {\n  catchError,\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  tap\n} from \"rxjs/operators\"\n\nimport { setSourceFacts, setSourceState } from \"~/actions\"\nimport { renderSourceFacts } from \"~/templates\"\nimport { digest } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\nimport { SourceFacts, fetchSourceFacts } from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n  facts: SourceFacts                   /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable<Source>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n  el: HTMLAnchorElement\n): Observable<Source> {\n  return fetch$ ||= defer(() => {\n    const data = sessionStorage.getItem(digest(\"__repo\"))\n    if (data) {\n      return of<SourceFacts>(JSON.parse(data))\n    } else {\n      const value$ = fetchSourceFacts(el.href)\n      value$.subscribe(value => {\n        try {\n          sessionStorage.setItem(digest(\"__repo\"), JSON.stringify(value))\n        } catch (err) {\n          /* Uncritical, just swallow */\n        }\n      })\n\n      /* Return value */\n      return value$\n    }\n  })\n    .pipe(\n      catchError(() => NEVER),\n      filter(facts => facts.length > 0),\n      map(facts => ({ facts })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n  el: HTMLAnchorElement\n): Observable<Component<Source>> {\n  const internal$ = new Subject<Source>()\n  internal$.subscribe(({ facts }) => {\n    setSourceFacts(el, renderSourceFacts(facts))\n    setSourceState(el, \"done\")\n  })\n\n  /* Create and return component */\n  return watchSource(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, animationFrameScheduler } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetTabsState, setTabsState } from \"~/actions\"\nimport { Viewport, watchViewportAt } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n  hidden: boolean                      /* User scrolled past tabs */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Tabs> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        return {\n          hidden: y >= 10\n        }\n      }),\n      distinctUntilKeyChanged(\"hidden\")\n    )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Tabs>> {\n  const internal$ = new Subject<Tabs>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setTabsState(el, \"hidden\")\n          else\n            resetTabsState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetTabsState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchTabs(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  scan,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetAnchorActive,\n  resetAnchorState,\n  setAnchorActive,\n  setAnchorState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElements,\n  watchElementSize\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n  prev: HTMLAnchorElement[][]          /* Anchors (previous) */\n  next: HTMLAnchorElement[][]          /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param anchors - Anchor elements\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n  anchors: HTMLAnchorElement[], { viewport$, header$ }: WatchOptions\n): Observable<TableOfContents> {\n  const table = new Map<HTMLAnchorElement, HTMLElement>()\n  for (const anchor of anchors) {\n    const id = decodeURIComponent(anchor.hash.substring(1))\n    const target = getElement(`[id=\"${id}\"]`)\n    if (typeof target !== \"undefined\")\n      table.set(anchor, target)\n  }\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(header => 24 + header.height)\n    )\n\n  /* Compute partition of previous and next anchors */\n  const partition$ = watchElementSize(document.body)\n    .pipe(\n      distinctUntilKeyChanged(\"height\"),\n\n      /* Build index to map anchor paths to vertical offsets */\n      map(() => {\n        let path: HTMLAnchorElement[] = []\n        return [...table].reduce((index, [anchor, target]) => {\n          while (path.length) {\n            const last = table.get(path[path.length - 1])!\n            if (last.tagName >= target.tagName) {\n              path.pop()\n            } else {\n              break\n            }\n          }\n\n          /* If the current anchor is hidden, continue with its parent */\n          let offset = target.offsetTop\n          while (!offset && target.parentElement) {\n            target = target.parentElement\n            offset = target.offsetTop\n          }\n\n          /* Map reversed anchor path to vertical offset */\n          return index.set(\n            [...path = [...path, anchor]].reverse(),\n            offset\n          )\n        }, new Map<HTMLAnchorElement[], number>())\n      }),\n\n      /* Re-compute partition when viewport offset changes */\n      switchMap(index => combineLatest([adjust$, viewport$])\n        .pipe(\n          scan(([prev, next], [adjust, { offset: { y } }]) => {\n\n            /* Look forward */\n            while (next.length) {\n              const [, offset] = next[0]\n              if (offset - adjust < y) {\n                prev = [...prev, next.shift()!]\n              } else {\n                break\n              }\n            }\n\n            /* Look backward */\n            while (prev.length) {\n              const [, offset] = prev[prev.length - 1]\n              if (offset - adjust >= y) {\n                next = [prev.pop()!, ...next]\n              } else {\n                break\n              }\n            }\n\n            /* Return partition */\n            return [prev, next]\n          }, [[], [...index]]),\n          distinctUntilChanged((a, b) => (\n            a[0] === b[0] &&\n            a[1] === b[1]\n          ))\n        )\n      )\n    )\n\n  /* Compute and return anchor list migrations */\n  return partition$\n    .pipe(\n      map(([prev, next]) => ({\n        prev: prev.map(([path]) => path),\n        next: next.map(([path]) => path)\n      })),\n\n      /* Extract anchor list migrations */\n      startWith({ prev: [], next: [] }),\n      bufferCount(2, 1),\n      map(([a, b]) => {\n\n        /* Moving down */\n        if (a.prev.length < b.prev.length) {\n          return {\n            prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n            next: []\n          }\n\n        /* Moving up */\n        } else {\n          return {\n            prev: b.prev.slice(-1),\n            next: b.next.slice(0, b.next.length - a.next.length)\n          }\n        }\n      })\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Anchor list element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<TableOfContents>> {\n  const internal$ = new Subject<TableOfContents>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n    )\n      .subscribe(({ prev, next }) => {\n\n        /* Look forward */\n        for (const [anchor] of next) {\n          resetAnchorActive(anchor)\n          resetAnchorState(anchor)\n        }\n\n        /* Look backward */\n        for (const [index, [anchor]] of prev.entries()) {\n          setAnchorActive(anchor, index === prev.length - 1)\n          setAnchorState(anchor, \"blur\")\n        }\n      })\n\n  /* Create and return component */\n  const anchors = getElements<HTMLAnchorElement>(\"[href^=\\\\#]\", el)\n  return watchTableOfContents(anchors, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  mapTo,\n  mergeMap,\n  switchMap,\n  takeWhile,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n  { document$, tablet$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements<HTMLInputElement>(\n        \"[data-md-state=indeterminate]\"\n      ))),\n      tap(el => {\n        el.indeterminate = true\n        el.checked = false\n      }),\n      mergeMap(el => fromEvent(el, \"change\")\n        .pipe(\n          takeWhile(() => el.hasAttribute(\"data-md-state\")),\n          mapTo(el)\n        )\n      ),\n      withLatestFrom(tablet$)\n    )\n      .subscribe(([el, tablet]) => {\n        el.removeAttribute(\"data-md-state\")\n        if (tablet)\n          el.checked = false\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  filter,\n  mapTo,\n  mergeMap,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n  return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n  { document$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements(\"[data-md-scrollfix]\"))),\n      tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n      filter(isAppleDevice),\n      mergeMap(el => fromEvent(el, \"touchstart\")\n        .pipe(\n          mapTo(el)\n        )\n      )\n    )\n      .subscribe(el => {\n        const top = el.scrollTop\n\n        /* We're at the top of the container */\n        if (top === 0) {\n          el.scrollTop = 1\n\n        /* We're at the bottom of the container */\n        } else if (top + el.offsetHeight === el.scrollHeight) {\n          el.scrollTop = top - 1\n        }\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  animationFrameScheduler,\n  combineLatest,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  map,\n  observeOn,\n  switchMap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetScrollLock, setScrollLock } from \"~/actions\"\nimport { Viewport, watchToggle } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n  { viewport$, tablet$ }: PatchOptions\n): void {\n  combineLatest([watchToggle(\"search\"), tablet$])\n    .pipe(\n      map(([active, tablet]) => active && !tablet),\n      switchMap(active => of(active)\n        .pipe(\n          delay(active ? 400 : 100),\n          observeOn(animationFrameScheduler)\n        )\n      ),\n      withLatestFrom(viewport$)\n    )\n      .subscribe(([active, { offset: { y }}]) => {\n        if (active)\n          setScrollLock(document.body, y)\n        else\n          resetScrollLock(document.body)\n      })\n}\n"],
-  "mappings": "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",
-  "names": []
-}
diff --git a/5.4/assets/javascripts/bundle.d892486b.min.js b/5.4/assets/javascripts/bundle.d892486b.min.js
new file mode 100644 (file)
index 0000000..a7d2639
--- /dev/null
@@ -0,0 +1,32 @@
+(()=>{var Ri=Object.create,ut=Object.defineProperty,Pi=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty,$i=Object.getOwnPropertyNames,Vi=Object.getOwnPropertyDescriptor,lt=Object.getOwnPropertySymbols,xr=Object.prototype.propertyIsEnumerable;var Sr=(e,t,r)=>t in e?ut(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))zt.call(t,r)&&Sr(e,r,t[r]);if(lt)for(var r of lt(t))xr.call(t,r)&&Sr(e,r,t[r]);return e},Di=e=>ut(e,"__esModule",{value:!0});var wr=(e,t)=>{var r={};for(var n in e)zt.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&lt)for(var n of lt(e))t.indexOf(n)<0&&xr.call(e,n)&&(r[n]=e[n]);return r},ft=(e,t)=>()=>(t||(t={exports:{}},e(t.exports,t)),t.exports);var Ui=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $i(t))!zt.call(e,n)&&n!=="default"&&ut(e,n,{get:()=>t[n],enumerable:!(r=Vi(t,n))||r.enumerable});return e},Be=e=>Ui(Di(ut(e!=null?Ri(Pi(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var Or=ft((Qt,Er)=>{(function(e,t){typeof Qt=="object"&&typeof Er!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Qt,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var at=T.type,st=T.tagName;return!!(st==="INPUT"&&a[at]&&!T.readOnly||st==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function u(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function l(T){!T.hasAttribute("data-focus-visible-added")||(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function m(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&u(r.activeElement),n=!0)}function f(T){n=!1}function h(T){!s(T.target)||(n||c(T.target))&&u(T.target)}function y(T){!s(T.target)||(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),l(T.target))}function b(T){document.visibilityState==="hidden"&&(o&&(n=!0),z())}function z(){document.addEventListener("mousemove",C),document.addEventListener("mousedown",C),document.addEventListener("mouseup",C),document.addEventListener("pointermove",C),document.addEventListener("pointerdown",C),document.addEventListener("pointerup",C),document.addEventListener("touchmove",C),document.addEventListener("touchstart",C),document.addEventListener("touchend",C)}function P(){document.removeEventListener("mousemove",C),document.removeEventListener("mousedown",C),document.removeEventListener("mouseup",C),document.removeEventListener("pointermove",C),document.removeEventListener("pointerdown",C),document.removeEventListener("pointerup",C),document.removeEventListener("touchmove",C),document.removeEventListener("touchstart",C),document.removeEventListener("touchend",C)}function C(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,P())}document.addEventListener("keydown",m,!0),document.addEventListener("mousedown",f,!0),document.addEventListener("pointerdown",f,!0),document.addEventListener("touchstart",f,!0),document.addEventListener("visibilitychange",b,!0),z(),r.addEventListener("focus",h,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Qr=ft((za,pt)=>{var Tr,_r,Mr,Ar,Lr,Hr,Cr,kr,jr,mt,qt,Fr,Ir,Rr,Pe,Pr,$r,Vr,Dr,Ur,Nr,Wr,zr,dt;(function(e){var t=typeof global=="object"?global:typeof self=="object"?self:typeof this=="object"?this:{};typeof define=="function"&&define.amd?define("tslib",["exports"],function(n){e(r(t,r(n)))}):typeof pt=="object"&&typeof pt.exports=="object"?e(r(t,r(pt.exports))):e(r(t));function r(n,o){return n!==t&&(typeof Object.create=="function"?Object.defineProperty(n,"__esModule",{value:!0}):n.__esModule=!0),function(i,a){return n[i]=o?o(i,a):a}}})(function(e){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,o){n.__proto__=o}||function(n,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(n[i]=o[i])};Tr=function(n,o){if(typeof o!="function"&&o!==null)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");t(n,o);function i(){this.constructor=n}n.prototype=o===null?Object.create(o):(i.prototype=o.prototype,new i)},_r=Object.assign||function(n){for(var o,i=1,a=arguments.length;i<a;i++){o=arguments[i];for(var s in o)Object.prototype.hasOwnProperty.call(o,s)&&(n[s]=o[s])}return n},Mr=function(n,o){var i={};for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&o.indexOf(a)<0&&(i[a]=n[a]);if(n!=null&&typeof Object.getOwnPropertySymbols=="function")for(var s=0,a=Object.getOwnPropertySymbols(n);s<a.length;s++)o.indexOf(a[s])<0&&Object.prototype.propertyIsEnumerable.call(n,a[s])&&(i[a[s]]=n[a[s]]);return i},Ar=function(n,o,i,a){var s=arguments.length,c=s<3?o:a===null?a=Object.getOwnPropertyDescriptor(o,i):a,u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(n,o,i,a);else for(var l=n.length-1;l>=0;l--)(u=n[l])&&(c=(s<3?u(c):s>3?u(o,i,c):u(o,i))||c);return s>3&&c&&Object.defineProperty(o,i,c),c},Lr=function(n,o){return function(i,a){o(i,a,n)}},Hr=function(n,o){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(n,o)},Cr=function(n,o,i,a){function s(c){return c instanceof i?c:new i(function(u){u(c)})}return new(i||(i=Promise))(function(c,u){function l(h){try{f(a.next(h))}catch(y){u(y)}}function m(h){try{f(a.throw(h))}catch(y){u(y)}}function f(h){h.done?c(h.value):s(h.value).then(l,m)}f((a=a.apply(n,o||[])).next())})},kr=function(n,o){var i={label:0,sent:function(){if(c[0]&1)throw c[1];return c[1]},trys:[],ops:[]},a,s,c,u;return u={next:l(0),throw:l(1),return:l(2)},typeof Symbol=="function"&&(u[Symbol.iterator]=function(){return this}),u;function l(f){return function(h){return m([f,h])}}function m(f){if(a)throw new TypeError("Generator is already executing.");for(;i;)try{if(a=1,s&&(c=f[0]&2?s.return:f[0]?s.throw||((c=s.return)&&c.call(s),0):s.next)&&!(c=c.call(s,f[1])).done)return c;switch(s=0,c&&(f=[f[0]&2,c.value]),f[0]){case 0:case 1:c=f;break;case 4:return i.label++,{value:f[1],done:!1};case 5:i.label++,s=f[1],f=[0];continue;case 7:f=i.ops.pop(),i.trys.pop();continue;default:if(c=i.trys,!(c=c.length>0&&c[c.length-1])&&(f[0]===6||f[0]===2)){i=0;continue}if(f[0]===3&&(!c||f[1]>c[0]&&f[1]<c[3])){i.label=f[1];break}if(f[0]===6&&i.label<c[1]){i.label=c[1],c=f;break}if(c&&i.label<c[2]){i.label=c[2],i.ops.push(f);break}c[2]&&i.ops.pop(),i.trys.pop();continue}f=o.call(n,i)}catch(h){f=[6,h],s=0}finally{a=c=0}if(f[0]&5)throw f[1];return{value:f[0]?f[1]:void 0,done:!0}}},jr=function(n,o){for(var i in n)i!=="default"&&!Object.prototype.hasOwnProperty.call(o,i)&&dt(o,n,i)},dt=Object.create?function(n,o,i,a){a===void 0&&(a=i),Object.defineProperty(n,a,{enumerable:!0,get:function(){return o[i]}})}:function(n,o,i,a){a===void 0&&(a=i),n[a]=o[i]},mt=function(n){var o=typeof Symbol=="function"&&Symbol.iterator,i=o&&n[o],a=0;if(i)return i.call(n);if(n&&typeof n.length=="number")return{next:function(){return n&&a>=n.length&&(n=void 0),{value:n&&n[a++],done:!n}}};throw new TypeError(o?"Object is not iterable.":"Symbol.iterator is not defined.")},qt=function(n,o){var i=typeof Symbol=="function"&&n[Symbol.iterator];if(!i)return n;var a=i.call(n),s,c=[],u;try{for(;(o===void 0||o-- >0)&&!(s=a.next()).done;)c.push(s.value)}catch(l){u={error:l}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(u)throw u.error}}return c},Fr=function(){for(var n=[],o=0;o<arguments.length;o++)n=n.concat(qt(arguments[o]));return n},Ir=function(){for(var n=0,o=0,i=arguments.length;o<i;o++)n+=arguments[o].length;for(var a=Array(n),s=0,o=0;o<i;o++)for(var c=arguments[o],u=0,l=c.length;u<l;u++,s++)a[s]=c[u];return a},Rr=function(n,o){for(var i=0,a=o.length,s=n.length;i<a;i++,s++)n[s]=o[i];return n},Pe=function(n){return this instanceof Pe?(this.v=n,this):new Pe(n)},Pr=function(n,o,i){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var a=i.apply(n,o||[]),s,c=[];return s={},u("next"),u("throw"),u("return"),s[Symbol.asyncIterator]=function(){return this},s;function u(b){a[b]&&(s[b]=function(z){return new Promise(function(P,C){c.push([b,z,P,C])>1||l(b,z)})})}function l(b,z){try{m(a[b](z))}catch(P){y(c[0][3],P)}}function m(b){b.value instanceof Pe?Promise.resolve(b.value.v).then(f,h):y(c[0][2],b)}function f(b){l("next",b)}function h(b){l("throw",b)}function y(b,z){b(z),c.shift(),c.length&&l(c[0][0],c[0][1])}},$r=function(n){var o,i;return o={},a("next"),a("throw",function(s){throw s}),a("return"),o[Symbol.iterator]=function(){return this},o;function a(s,c){o[s]=n[s]?function(u){return(i=!i)?{value:Pe(n[s](u)),done:s==="return"}:c?c(u):u}:c}},Vr=function(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=n[Symbol.asyncIterator],i;return o?o.call(n):(n=typeof mt=="function"?mt(n):n[Symbol.iterator](),i={},a("next"),a("throw"),a("return"),i[Symbol.asyncIterator]=function(){return this},i);function a(c){i[c]=n[c]&&function(u){return new Promise(function(l,m){u=n[c](u),s(l,m,u.done,u.value)})}}function s(c,u,l,m){Promise.resolve(m).then(function(f){c({value:f,done:l})},u)}},Dr=function(n,o){return Object.defineProperty?Object.defineProperty(n,"raw",{value:o}):n.raw=o,n};var r=Object.create?function(n,o){Object.defineProperty(n,"default",{enumerable:!0,value:o})}:function(n,o){n.default=o};Ur=function(n){if(n&&n.__esModule)return n;var o={};if(n!=null)for(var i in n)i!=="default"&&Object.prototype.hasOwnProperty.call(n,i)&&dt(o,n,i);return r(o,n),o},Nr=function(n){return n&&n.__esModule?n:{default:n}},Wr=function(n,o){if(!o.has(n))throw new TypeError("attempted to get private field on non-instance");return o.get(n)},zr=function(n,o,i){if(!o.has(n))throw new TypeError("attempted to set private field on non-instance");return o.set(n,i),i},e("__extends",Tr),e("__assign",_r),e("__rest",Mr),e("__decorate",Ar),e("__param",Lr),e("__metadata",Hr),e("__awaiter",Cr),e("__generator",kr),e("__exportStar",jr),e("__createBinding",dt),e("__values",mt),e("__read",qt),e("__spread",Fr),e("__spreadArrays",Ir),e("__spreadArray",Rr),e("__await",Pe),e("__asyncGenerator",Pr),e("__asyncDelegator",$r),e("__asyncValues",Vr),e("__makeTemplateObject",Dr),e("__importStar",Ur),e("__importDefault",Nr),e("__classPrivateFieldGet",Wr),e("__classPrivateFieldSet",zr)})});var lr=ft((it,ur)=>{(function(t,r){typeof it=="object"&&typeof ur=="object"?ur.exports=r():typeof define=="function"&&define.amd?define([],r):typeof it=="object"?it.ClipboardJS=r():t.ClipboardJS=r()})(it,function(){return function(){var e={134:function(n,o,i){"use strict";i.d(o,{default:function(){return Fi}});var a=i(279),s=i.n(a),c=i(370),u=i.n(c),l=i(817),m=i.n(l);function f(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?f=function(d){return typeof d}:f=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},f(w)}function h(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function y(w,v){for(var d=0;d<v.length;d++){var A=v[d];A.enumerable=A.enumerable||!1,A.configurable=!0,"value"in A&&(A.writable=!0),Object.defineProperty(w,A.key,A)}}function b(w,v,d){return v&&y(w.prototype,v),d&&y(w,d),w}var z=function(){function w(v){h(this,w),this.resolveOptions(v),this.initSelection()}return b(w,[{key:"resolveOptions",value:function(){var d=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=d.action,this.container=d.container,this.emitter=d.emitter,this.target=d.target,this.text=d.text,this.trigger=d.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"createFakeElement",value:function(){var d=document.documentElement.getAttribute("dir")==="rtl";this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[d?"right":"left"]="-9999px";var A=window.pageYOffset||document.documentElement.scrollTop;return this.fakeElem.style.top="".concat(A,"px"),this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.fakeElem}},{key:"selectFake",value:function(){var d=this,A=this.createFakeElement();this.fakeHandlerCallback=function(){return d.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.container.appendChild(A),this.selectedText=m()(A),this.copyText(),this.removeFake()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=m()(this.target),this.copyText()}},{key:"copyText",value:function(){var d;try{d=document.execCommand(this.action)}catch(A){d=!1}this.handleResult(d)}},{key:"handleResult",value:function(d){this.emitter.emit(d?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var d=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"copy";if(this._action=d,this._action!=="copy"&&this._action!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(d){if(d!==void 0)if(d&&f(d)==="object"&&d.nodeType===1){if(this.action==="copy"&&d.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(this.action==="cut"&&(d.hasAttribute("readonly")||d.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`);this._target=d}else throw new Error('Invalid "target" value, use a valid Element')},get:function(){return this._target}}]),w}(),P=z;function C(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(d){return typeof d}:C=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},C(w)}function T(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function at(w,v){for(var d=0;d<v.length;d++){var A=v[d];A.enumerable=A.enumerable||!1,A.configurable=!0,"value"in A&&(A.writable=!0),Object.defineProperty(w,A.key,A)}}function st(w,v,d){return v&&at(w.prototype,v),d&&at(w,d),w}function Ai(w,v){if(typeof v!="function"&&v!==null)throw new TypeError("Super expression must either be null or a function");w.prototype=Object.create(v&&v.prototype,{constructor:{value:w,writable:!0,configurable:!0}}),v&&Nt(w,v)}function Nt(w,v){return Nt=Object.setPrototypeOf||function(A,I){return A.__proto__=I,A},Nt(w,v)}function Li(w){var v=ki();return function(){var A=ct(w),I;if(v){var X=ct(this).constructor;I=Reflect.construct(A,arguments,X)}else I=A.apply(this,arguments);return Hi(this,I)}}function Hi(w,v){return v&&(C(v)==="object"||typeof v=="function")?v:Ci(w)}function Ci(w){if(w===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return w}function ki(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(w){return!1}}function ct(w){return ct=Object.setPrototypeOf?Object.getPrototypeOf:function(d){return d.__proto__||Object.getPrototypeOf(d)},ct(w)}function Wt(w,v){var d="data-clipboard-".concat(w);if(!!v.hasAttribute(d))return v.getAttribute(d)}var ji=function(w){Ai(d,w);var v=Li(d);function d(A,I){var X;return T(this,d),X=v.call(this),X.resolveOptions(I),X.listenClick(A),X}return st(d,[{key:"resolveOptions",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=C(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var X=this;this.listener=u()(I,"click",function(Ye){return X.onClick(Ye)})}},{key:"onClick",value:function(I){var X=I.delegateTarget||I.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new P({action:this.action(X),target:this.target(X),text:this.text(X),container:this.container,trigger:X,emitter:this})}},{key:"defaultAction",value:function(I){return Wt("action",I)}},{key:"defaultTarget",value:function(I){var X=Wt("target",I);if(X)return document.querySelector(X)}},{key:"defaultText",value:function(I){return Wt("text",I)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],X=typeof I=="string"?[I]:I,Ye=!!document.queryCommandSupported;return X.forEach(function(Ii){Ye=Ye&&!!document.queryCommandSupported(Ii)}),Ye}}]),d}(s()),Fi=ji},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(l,m,f,h,y){var b=u.apply(this,arguments);return l.addEventListener(f,b,y),{destroy:function(){l.removeEventListener(f,b,y)}}}function c(l,m,f,h,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof f=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(b){return s(b,m,f,h,y)}))}function u(l,m,f,h){return function(y){y.delegateTarget=a(y.target,m),y.delegateTarget&&h.call(l,y)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function c(f,h,y){if(!f&&!h&&!y)throw new Error("Missing required arguments");if(!a.string(h))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(f))return u(f,h,y);if(a.nodeList(f))return l(f,h,y);if(a.string(f))return m(f,h,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function u(f,h,y){return f.addEventListener(h,y),{destroy:function(){f.removeEventListener(h,y)}}}function l(f,h,y){return Array.prototype.forEach.call(f,function(b){b.addEventListener(h,y)}),{destroy:function(){Array.prototype.forEach.call(f,function(b){b.removeEventListener(h,y)})}}}function m(f,h,y){return s(document.body,f,h,y)}n.exports=c},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),u=document.createRange();u.selectNodeContents(i),c.removeAllRanges(),c.addRange(u),a=c.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function u(){c.off(i,u),a.apply(s,arguments)}return u._=a,this.on(i,u,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,u=s.length;for(c;c<u;c++)s[c].fn.apply(s[c].ctx,a);return this},off:function(i,a){var s=this.e||(this.e={}),c=s[i],u=[];if(c&&a)for(var l=0,m=c.length;l<m;l++)c[l].fn!==a&&c[l].fn._!==a&&u.push(c[l]);return u.length?s[i]=u:delete s[i],this}},n.exports=o,n.exports.TinyEmitter=o}},t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}return function(){r.n=function(n){var o=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(o,{a:o}),o}}(),function(){r.d=function(n,o){for(var i in o)r.o(o,i)&&!r.o(n,i)&&Object.defineProperty(n,i,{enumerable:!0,get:o[i]})}}(),function(){r.o=function(n,o){return Object.prototype.hasOwnProperty.call(n,o)}}(),r(134)}().default})});var oi=ft((vg,ni)=>{"use strict";var Aa=/["'&<>]/;ni.exports=La;function La(e){var t=""+e,r=Aa.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i<t.length;i++){switch(t.charCodeAt(i)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}a!==i&&(o+=t.substring(a,i)),a=i+1,o+=n}return a!==i?o+t.substring(a,i):o}});var CS=Be(Or());var qr=Be(Qr()),{__extends:K,__assign:qa,__rest:Ka,__decorate:Ja,__param:Ya,__metadata:Ba,__awaiter:Kr,__generator:Jr,__exportStar:Ga,__createBinding:Xa,__values:ce,__read:H,__spread:Za,__spreadArrays:es,__spreadArray:j,__await:ts,__asyncGenerator:rs,__asyncDelegator:ns,__asyncValues:Yr,__makeTemplateObject:os,__importStar:is,__importDefault:as,__classPrivateFieldGet:ss,__classPrivateFieldSet:cs}=qr.default;function S(e){return typeof e=="function"}function ht(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var bt=ht(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription:
+`+r.map(function(n,o){return o+1+") "+n.toString()}).join(`
+  `):"",this.name="UnsubscriptionError",this.errors=r}});function xe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var oe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._teardowns=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(Array.isArray(a))try{for(var s=ce(a),c=s.next();!c.done;c=s.next()){var u=c.value;u.remove(this)}}catch(b){t={error:b}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a==null||a.remove(this);var l=this.initialTeardown;if(S(l))try{l()}catch(b){i=b instanceof bt?b.errors:[b]}var m=this._teardowns;if(m){this._teardowns=null;try{for(var f=ce(m),h=f.next();!h.done;h=f.next()){var y=h.value;try{Br(y)}catch(b){i=i!=null?i:[],b instanceof bt?i=j(j([],H(i)),H(b.errors)):i.push(b)}}}catch(b){n={error:b}}finally{try{h&&!h.done&&(o=f.return)&&o.call(f)}finally{if(n)throw n.error}}}if(i)throw new bt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)Br(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._teardowns=(r=this._teardowns)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&xe(r,t)},e.prototype.remove=function(t){var r=this._teardowns;r&&xe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Kt=oe.EMPTY;function vt(e){return e instanceof oe||e&&"closed"in e&&S(e.remove)&&S(e.add)&&S(e.unsubscribe)}function Br(e){S(e)?e():e.unsubscribe()}var de={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var $e={setTimeout:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.setTimeout)||setTimeout).apply(void 0,j([],H(e)))},clearTimeout:function(e){var t=$e.delegate;return((t==null?void 0:t.clearTimeout)||clearTimeout)(e)},delegate:void 0};function gt(e){$e.setTimeout(function(){var t=de.onUnhandledError;if(t)t(e);else throw e})}function Z(){}var Gr=function(){return Jt("C",void 0,void 0)}();function Xr(e){return Jt("E",void 0,e)}function Zr(e){return Jt("N",e,void 0)}function Jt(e,t,r){return{kind:e,value:t,error:r}}var Ge=function(e){K(t,e);function t(r){var n=e.call(this)||this;return n.isStopped=!1,r?(n.destination=r,vt(r)&&r.add(n)):n.destination=Ni,n}return t.create=function(r,n,o){return new Yt(r,n,o)},t.prototype.next=function(r){this.isStopped?Bt(Zr(r),this):this._next(r)},t.prototype.error=function(r){this.isStopped?Bt(Xr(r),this):(this.isStopped=!0,this._error(r))},t.prototype.complete=function(){this.isStopped?Bt(Gr,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this),this.destination=null)},t.prototype._next=function(r){this.destination.next(r)},t.prototype._error=function(r){try{this.destination.error(r)}finally{this.unsubscribe()}},t.prototype._complete=function(){try{this.destination.complete()}finally{this.unsubscribe()}},t}(oe);var Yt=function(e){K(t,e);function t(r,n,o){var i=e.call(this)||this,a;if(S(r))a=r;else if(r){a=r.next,n=r.error,o=r.complete;var s;i&&de.useDeprecatedNextContext?(s=Object.create(r),s.unsubscribe=function(){return i.unsubscribe()}):s=r,a=a==null?void 0:a.bind(s),n=n==null?void 0:n.bind(s),o=o==null?void 0:o.bind(s)}return i.destination={next:a?Gt(a,i):Z,error:Gt(n!=null?n:en,i),complete:o?Gt(o,i):Z},i}return t}(Ge);function Gt(e,t){return function(){for(var r=[],n=0;n<arguments.length;n++)r[n]=arguments[n];try{e.apply(void 0,j([],H(r)))}catch(o){if(de.useDeprecatedSynchronousErrorHandling)if(t._syncErrorHack_isSubscribing)t.__syncError=o;else throw o;else gt(o)}}}function en(e){throw e}function Bt(e,t){var r=de.onStoppedNotification;r&&$e.setTimeout(function(){return r(e,t)})}var Ni={closed:!0,next:Z,error:en,complete:Z};var Ee=function(){return typeof Symbol=="function"&&Symbol.observable||"@@observable"}();function ue(e){return e}function tn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Xt(e)}function Xt(e){return e.length===0?ue:e.length===1?e[0]:function(r){return e.reduce(function(n,o){return o(n)},r)}}var M=function(){function e(t){t&&(this._subscribe=t)}return e.prototype.lift=function(t){var r=new e;return r.source=this,r.operator=t,r},e.prototype.subscribe=function(t,r,n){var o=Wi(t)?t:new Yt(t,r,n),i=this,a=i.operator,s=i.source,c=o;if(de.useDeprecatedSynchronousErrorHandling&&(c._syncErrorHack_isSubscribing=!0),o.add(a?a.call(o,s):s||de.useDeprecatedSynchronousErrorHandling?this._subscribe(o):this._trySubscribe(o)),de.useDeprecatedSynchronousErrorHandling)for(c._syncErrorHack_isSubscribing=!1;c;){if(c.__syncError)throw c.__syncError;c=c.destination}return o},e.prototype._trySubscribe=function(t){try{return this._subscribe(t)}catch(r){t.error(r)}},e.prototype.forEach=function(t,r){var n=this;return r=rn(r),new r(function(o,i){var a;a=n.subscribe(function(s){try{t(s)}catch(c){i(c),a==null||a.unsubscribe()}},i,o)})},e.prototype._subscribe=function(t){var r;return(r=this.source)===null||r===void 0?void 0:r.subscribe(t)},e.prototype[Ee]=function(){return this},e.prototype.pipe=function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return t.length?Xt(t)(this):this},e.prototype.toPromise=function(t){var r=this;return t=rn(t),new t(function(n,o){var i;r.subscribe(function(a){return i=a},function(a){return o(a)},function(){return n(i)})})},e.create=function(t){return new e(t)},e}();function rn(e){var t;return(t=e!=null?e:de.Promise)!==null&&t!==void 0?t:Promise}function zi(e){return e&&S(e.next)&&S(e.error)&&S(e.complete)}function Wi(e){return e&&e instanceof Ge||zi(e)&&vt(e)}function Qi(e){return S(e==null?void 0:e.lift)}function g(e){return function(t){if(Qi(t))return t.lift(function(r){try{return e(r,this)}catch(n){this.error(n)}});throw new TypeError("Unable to lift unknown Observable type")}}var x=function(e){K(t,e);function t(r,n,o,i,a){var s=e.call(this,r)||this;return s.onFinalize=a,s._next=n?function(c){try{n(c)}catch(u){r.error(u)}}:e.prototype._next,s._error=o?function(c){try{o(c)}catch(u){r.error(u)}finally{this.unsubscribe()}}:e.prototype._error,s._complete=i?function(){try{i()}catch(c){r.error(c)}finally{this.unsubscribe()}}:e.prototype._complete,s}return t.prototype.unsubscribe=function(){var r,n=this.closed;e.prototype.unsubscribe.call(this),!n&&((r=this.onFinalize)===null||r===void 0||r.call(this))},t}(Ge);var Ve={schedule:function(e){var t=requestAnimationFrame,r=cancelAnimationFrame,n=Ve.delegate;n&&(t=n.requestAnimationFrame,r=n.cancelAnimationFrame);var o=t(function(i){r=void 0,e(i)});return new oe(function(){return r==null?void 0:r(o)})},requestAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ve.delegate;return((r==null?void 0:r.requestAnimationFrame)||requestAnimationFrame).apply(void 0,j([],H(e)))},cancelAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ve.delegate;return((r==null?void 0:r.cancelAnimationFrame)||cancelAnimationFrame).apply(void 0,j([],H(e)))},delegate:void 0};var nn=ht(function(e){return function(){e(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"}});var _=function(e){K(t,e);function t(){var r=e.call(this)||this;return r.observers=[],r.closed=!1,r.isStopped=!1,r.hasError=!1,r.thrownError=null,r}return t.prototype.lift=function(r){var n=new on(this,this);return n.operator=r,n},t.prototype._throwIfClosed=function(){if(this.closed)throw new nn},t.prototype.next=function(r){var n,o;if(this._throwIfClosed(),!this.isStopped){var i=this.observers.slice();try{for(var a=ce(i),s=a.next();!s.done;s=a.next()){var c=s.value;c.next(r)}}catch(u){n={error:u}}finally{try{s&&!s.done&&(o=a.return)&&o.call(a)}finally{if(n)throw n.error}}}},t.prototype.error=function(r){if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=r;for(var n=this.observers;n.length;)n.shift().error(r)}},t.prototype.complete=function(){if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;for(var r=this.observers;r.length;)r.shift().complete()}},t.prototype.unsubscribe=function(){this.isStopped=this.closed=!0,this.observers=null},t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Kt:(s.push(r),new oe(function(){return xe(n.observers,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new M;return r.source=this,r},t.create=function(r,n){return new on(r,n)},t}(M);var on=function(e){K(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Kt},t}(_);var Xe={now:function(){return(Xe.delegate||Date).now()},delegate:void 0};var yt=function(e){K(t,e);function t(r,n,o){r===void 0&&(r=Infinity),n===void 0&&(n=Infinity),o===void 0&&(o=Xe);var i=e.call(this)||this;return i.bufferSize=r,i.windowTime=n,i.timestampProvider=o,i.buffer=[],i.infiniteTimeWindow=!0,i.infiniteTimeWindow=n===Infinity,i.bufferSize=Math.max(1,r),i.windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n.buffer,a=n.infiniteTimeWindow,s=n.timestampProvider,c=n.windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this.trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this.trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o.infiniteTimeWindow,a=o.buffer,s=a.slice(),c=0;c<s.length&&!r.closed;c+=i?1:2)r.next(s[c]);return this._checkFinalizedStatuses(r),n},t.prototype.trimBuffer=function(){var r=this,n=r.bufferSize,o=r.timestampProvider,i=r.buffer,a=r.infiniteTimeWindow,s=(a?1:2)*n;if(n<Infinity&&s<i.length&&i.splice(0,i.length-s),!a){for(var c=o.now(),u=0,l=1;l<i.length&&i[l]<=c;l+=2)u=l;u&&i.splice(0,u+1)}},t}(_);var an=function(e){K(t,e);function t(r,n){return e.call(this)||this}return t.prototype.schedule=function(r,n){return n===void 0&&(n=0),this},t}(oe);var Ze={setInterval:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ze.delegate;return((r==null?void 0:r.setInterval)||setInterval).apply(void 0,j([],H(e)))},clearInterval:function(e){var t=Ze.delegate;return((t==null?void 0:t.clearInterval)||clearInterval)(e)},delegate:void 0};var xt=function(e){K(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o.pending=!1,o}return t.prototype.schedule=function(r,n){if(n===void 0&&(n=0),this.closed)return this;this.state=r;var o=this.id,i=this.scheduler;return o!=null&&(this.id=this.recycleAsyncId(i,o,n)),this.pending=!0,this.delay=n,this.id=this.id||this.requestAsyncId(i,this.id,n),this},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),Ze.setInterval(r.flush.bind(r,this),o)},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&this.delay===o&&this.pending===!1)return n;Ze.clearInterval(n)},t.prototype.execute=function(r,n){if(this.closed)return new Error("executing a cancelled action");this.pending=!1;var o=this._execute(r,n);if(o)return o;this.pending===!1&&this.id!=null&&(this.id=this.recycleAsyncId(this.scheduler,this.id,null))},t.prototype._execute=function(r,n){var o=!1,i;try{this.work(r)}catch(a){o=!0,i=!!a&&a||new Error(a)}if(o)return this.unsubscribe(),i},t.prototype.unsubscribe=function(){if(!this.closed){var r=this,n=r.id,o=r.scheduler,i=o.actions;this.work=this.state=this.scheduler=null,this.pending=!1,xe(i,this),n!=null&&(this.id=this.recycleAsyncId(o,n,null)),this.delay=null,e.prototype.unsubscribe.call(this)}},t}(an);var Zt=function(){function e(t,r){r===void 0&&(r=e.now),this.schedulerActionCtor=t,this.now=r}return e.prototype.schedule=function(t,r,n){return r===void 0&&(r=0),new this.schedulerActionCtor(this,t).schedule(n,r)},e.now=Xe.now,e}();var St=function(e){K(t,e);function t(r,n){n===void 0&&(n=Zt.now);var o=e.call(this,r,n)||this;return o.actions=[],o.active=!1,o.scheduled=void 0,o}return t.prototype.flush=function(r){var n=this.actions;if(this.active){n.push(r);return}var o;this.active=!0;do if(o=r.execute(r.state,r.delay))break;while(r=n.shift());if(this.active=!1,o){for(;r=n.shift();)r.unsubscribe();throw o}},t}(Zt);var et=new St(xt),sn=et;var cn=function(e){K(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r.scheduled||(r.scheduled=Ve.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.length===0&&(Ve.cancelAnimationFrame(n),r.scheduled=void 0)},t}(xt);var un=function(e){K(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this.active=!0,this.scheduled=void 0;var n=this.actions,o,i=-1;r=r||n.shift();var a=n.length;do if(o=r.execute(r.state,r.delay))break;while(++i<a&&(r=n.shift()));if(this.active=!1,o){for(;++i<a&&(r=n.shift());)r.unsubscribe();throw o}},t}(St);var J=new un(cn);var he=new M(function(e){return e.complete()});function De(e,t){return new M(function(r){var n=0;return t.schedule(function(){n===e.length?r.complete():(r.next(e[n++]),r.closed||this.schedule())})})}var Ue=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function wt(e){return S(e==null?void 0:e.then)}function ln(e,t){return new M(function(r){var n=new oe;return n.add(t.schedule(function(){var o=e[Ee]();n.add(o.subscribe({next:function(i){n.add(t.schedule(function(){return r.next(i)}))},error:function(i){n.add(t.schedule(function(){return r.error(i)}))},complete:function(){n.add(t.schedule(function(){return r.complete()}))}}))})),n})}function fn(e,t){return new M(function(r){return t.schedule(function(){return e.then(function(n){r.add(t.schedule(function(){r.next(n),r.add(t.schedule(function(){return r.complete()}))}))},function(n){r.add(t.schedule(function(){return r.error(n)}))})})})}function qi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Et=qi();function pn(e,t,r,n){n===void 0&&(n=0);var o=t.schedule(function(){try{r.call(this)}catch(i){e.error(i)}},n);return e.add(o),o}function mn(e,t){return new M(function(r){var n;return r.add(t.schedule(function(){n=e[Et](),pn(r,t,function(){var o=n.next(),i=o.value,a=o.done;a?r.complete():(r.next(i),this.schedule())})})),function(){return S(n==null?void 0:n.return)&&n.return()}})}function Ot(e){return S(e[Ee])}function Tt(e){return S(e==null?void 0:e[Et])}function dn(e,t){if(!e)throw new Error("Iterable cannot be null");return new M(function(r){var n=new oe;return n.add(t.schedule(function(){var o=e[Symbol.asyncIterator]();n.add(t.schedule(function(){var i=this;o.next().then(function(a){a.done?r.complete():(r.next(a.value),i.schedule())})}))})),n})}function _t(e){return Symbol.asyncIterator&&S(e==null?void 0:e[Symbol.asyncIterator])}function Mt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, Array, AsyncIterable, or Iterable.")}function hn(e,t){if(e!=null){if(Ot(e))return ln(e,t);if(Ue(e))return De(e,t);if(wt(e))return fn(e,t);if(_t(e))return dn(e,t);if(Tt(e))return mn(e,t)}throw Mt(e)}function Se(e,t){return t?hn(e,t):W(e)}function W(e){if(e instanceof M)return e;if(e!=null){if(Ot(e))return Ki(e);if(Ue(e))return er(e);if(wt(e))return Ji(e);if(_t(e))return Bi(e);if(Tt(e))return Yi(e)}throw Mt(e)}function Ki(e){return new M(function(t){var r=e[Ee]();if(S(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function er(e){return new M(function(t){for(var r=0;r<e.length&&!t.closed;r++)t.next(e[r]);t.complete()})}function Ji(e){return new M(function(t){e.then(function(r){t.closed||(t.next(r),t.complete())},function(r){return t.error(r)}).then(null,gt)})}function Yi(e){return new M(function(t){var r,n;try{for(var o=ce(e),i=o.next();!i.done;i=o.next()){var a=i.value;if(t.next(a),t.closed)return}}catch(s){r={error:s}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}t.complete()})}function Bi(e){return new M(function(t){Gi(e,t).catch(function(r){return t.error(r)})})}function Gi(e,t){var r,n,o,i;return Kr(this,void 0,void 0,function(){var a,s;return Jr(this,function(c){switch(c.label){case 0:c.trys.push([0,5,6,11]),r=Yr(e),c.label=1;case 1:return[4,r.next()];case 2:if(n=c.sent(),!!n.done)return[3,4];if(a=n.value,t.next(a),t.closed)return[2];c.label=3;case 3:return[3,1];case 4:return[3,11];case 5:return s=c.sent(),o={error:s},[3,11];case 6:return c.trys.push([6,,9,10]),n&&!n.done&&(i=r.return)?[4,i.call(r)]:[3,8];case 7:c.sent(),c.label=8;case 8:return[3,10];case 9:if(o)throw o.error;return[7];case 10:return[7];case 11:return t.complete(),[2]}})})}function be(e,t){return t?De(e,t):er(e)}function At(e){return e&&S(e.schedule)}function tr(e){return e[e.length-1]}function Oe(e){return S(tr(e))?e.pop():void 0}function fe(e){return At(tr(e))?e.pop():void 0}function Lt(e,t){return typeof tr(e)=="number"?e.pop():t}function F(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e);return r?De(e,r):be(e)}function bn(e){return e instanceof Date&&!isNaN(e)}function p(e,t){return g(function(r,n){var o=0;r.subscribe(new x(n,function(i){n.next(e.call(t,i,o++))}))})}var Xi=Array.isArray;function Zi(e,t){return Xi(t)?e.apply(void 0,j([],H(t))):e(t)}function Ne(e){return p(function(t){return Zi(e,t)})}function Y(e,t){return t===void 0&&(t=0),g(function(r,n){r.subscribe(new x(n,function(o){return n.add(e.schedule(function(){return n.next(o)},t))},function(o){return n.add(e.schedule(function(){return n.error(o)},t))},function(){return n.add(e.schedule(function(){return n.complete()},t))}))})}var ea=Array.isArray,ta=Object.getPrototypeOf,ra=Object.prototype,na=Object.keys;function vn(e){if(e.length===1){var t=e[0];if(ea(t))return{args:t,keys:null};if(oa(t)){var r=na(t);return{args:r.map(function(n){return t[n]}),keys:r}}}return{args:e,keys:null}}function oa(e){return e&&typeof e=="object"&&ta(e)===ra}function gn(e,t){return e.reduce(function(r,n,o){return r[n]=t[o],r},{})}function B(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Oe(e),o=vn(e),i=o.args,a=o.keys;if(i.length===0)return Se([],r);var s=new M(rr(i,r,a?function(c){return gn(a,c)}:ue));return n?s.pipe(Ne(n)):s}function rr(e,t,r){return r===void 0&&(r=ue),function(n){yn(t,function(){for(var o=e.length,i=new Array(o),a=o,s=o,c=function(l){yn(t,function(){var m=Se(e[l],t),f=!1;m.subscribe(new x(n,function(h){i[l]=h,f||(f=!0,s--),s||n.next(r(i.slice()))},void 0,function(){--a||n.complete()}))},n)},u=0;u<o;u++)c(u)},n)}}function yn(e,t,r){e?r.add(e.schedule(t)):t()}function xn(e,t,r,n,o,i,a,s){var c=[],u=0,l=0,m=!1,f=function(){m&&!c.length&&!u&&t.complete()},h=function(b){return u<n?y(b):c.push(b)},y=function(b){i&&t.next(b),u++;var z=!1;W(r(b,l++)).subscribe(new x(t,function(P){o==null||o(P),i?h(P):t.next(P)},void 0,function(){z=!0},function(){if(z)try{u--;for(var P=function(){var C=c.shift();a?t.add(a.schedule(function(){return y(C)})):y(C)};c.length&&u<n;)P();f()}catch(C){t.error(C)}}))};return e.subscribe(new x(t,h,void 0,function(){m=!0,f()})),function(){s==null||s()}}function te(e,t,r){return r===void 0&&(r=Infinity),S(t)?te(function(n,o){return p(function(i,a){return t(n,i,o,a)})(W(e(n,o)))},r):(typeof t=="number"&&(r=t),g(function(n,o){return xn(n,o,e,r)}))}function We(e){return e===void 0&&(e=Infinity),te(ue,e)}function Sn(){return We(1)}function tt(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Sn()(be(e,fe(e)))}function Te(e){return new M(function(t){W(e()).subscribe(t)})}var ia=["addListener","removeListener"],aa=["addEventListener","removeEventListener"],sa=["on","off"];function O(e,t,r,n){if(S(r)&&(n=r,r=void 0),n)return O(e,t,r).pipe(Ne(n));var o=H(la(e)?aa.map(function(s){return function(c){return e[s](t,c,r)}}):ca(e)?ia.map(wn(e,t)):ua(e)?sa.map(wn(e,t)):[],2),i=o[0],a=o[1];if(!i&&Ue(e))return te(function(s){return O(s,t,r)})(be(e));if(!i)throw new TypeError("Invalid event target");return new M(function(s){var c=function(){for(var u=[],l=0;l<arguments.length;l++)u[l]=arguments[l];return s.next(1<u.length?u:u[0])};return i(c),function(){return a(c)}})}function wn(e,t){return function(r){return function(n){return e[r](t,n)}}}function ca(e){return S(e.addListener)&&S(e.removeListener)}function ua(e){return S(e.on)&&S(e.off)}function la(e){return S(e.addEventListener)&&S(e.removeEventListener)}function En(e,t,r){e===void 0&&(e=0),r===void 0&&(r=sn);var n=-1;return t!=null&&(At(t)?r=t:n=t),new M(function(o){var i=bn(e)?+e-r.now():e;i<0&&(i=0);var a=0;return r.schedule(function(){o.closed||(o.next(a++),0<=n?this.schedule(void 0,n):o.complete())},i)})}var fa=Array.isArray;function _e(e){return e.length===1&&fa(e[0])?e[0]:e}function R(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Lt(e,Infinity),o=_e(e);return o.length?o.length===1?W(o[0]):We(n)(be(o,r)):he}var ee=new M(Z);function L(e,t){return g(function(r,n){var o=0;r.subscribe(new x(n,function(i){return e.call(t,i,o++)&&n.next(i)}))})}function Ht(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e),n=_e(e);return n.length?new M(function(o){var i=n.map(function(){return[]}),a=n.map(function(){return!1});o.add(function(){i=a=null});for(var s=function(u){W(n[u]).subscribe(new x(o,function(l){if(i[u].push(l),i.every(function(f){return f.length})){var m=i.map(function(f){return f.shift()});o.next(r?r.apply(void 0,j([],H(m))):m),i.some(function(f,h){return!f.length&&a[h]})&&o.complete()}},void 0,function(){a[u]=!0,!i[u].length&&o.complete()}))},c=0;!o.closed&&c<n.length;c++)s(c);return function(){i=a=null}}):he}function ve(e,t){return t===void 0&&(t=null),t=t!=null?t:e,g(function(r,n){var o=[],i=0;r.subscribe(new x(n,function(a){var s,c,u,l,m=null;i++%t==0&&o.push([]);try{for(var f=ce(o),h=f.next();!h.done;h=f.next()){var y=h.value;y.push(a),e<=y.length&&(m=m!=null?m:[],m.push(y))}}catch(P){s={error:P}}finally{try{h&&!h.done&&(c=f.return)&&c.call(f)}finally{if(s)throw s.error}}if(m)try{for(var b=ce(m),z=b.next();!z.done;z=b.next()){var y=z.value;xe(o,y),n.next(y)}}catch(P){u={error:P}}finally{try{z&&!z.done&&(l=b.return)&&l.call(b)}finally{if(u)throw u.error}}},void 0,function(){var a,s;try{for(var c=ce(o),u=c.next();!u.done;u=c.next()){var l=u.value;n.next(l)}}catch(m){a={error:m}}finally{try{u&&!u.done&&(s=c.return)&&s.call(c)}finally{if(a)throw a.error}}n.complete()},function(){o=null}))})}function rt(e){return g(function(t,r){var n=null,o=!1,i;n=t.subscribe(new x(r,void 0,function(a){i=W(e(a,rt(e)(t))),n?(n.unsubscribe(),n=null,i.subscribe(r)):o=!0})),o&&(n.unsubscribe(),n=null,i.subscribe(r))})}function On(e,t,r,n,o){return function(i,a){var s=r,c=t,u=0;i.subscribe(new x(a,function(l){var m=u++;c=s?e(c,l,m):(s=!0,l),n&&a.next(c)},void 0,o&&function(){s&&a.next(c),a.complete()}))}}function nr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e);return r?tn(nr.apply(void 0,j([],H(e))),Ne(r)):g(function(n,o){rr(j([n],H(_e(e))))(o)})}function or(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return nr.apply(void 0,j([],H(e)))}function Tn(e,t){return S(t)?te(e,t,1):te(e,1)}function _n(e,t){return t===void 0&&(t=et),g(function(r,n){var o=null,i=null,a=null,s=function(){if(o){o.unsubscribe(),o=null;var u=i;i=null,n.next(u)}};function c(){var u=a+e,l=t.now();if(l<u){o=this.schedule(void 0,u-l);return}s()}r.subscribe(new x(n,function(u){i=u,a=t.now(),o||(o=t.schedule(c,e))},void 0,function(){s(),n.complete()},function(){i=o=null}))})}function ze(e){return g(function(t,r){var n=!1;t.subscribe(new x(r,function(o){n=!0,r.next(o)},void 0,function(){n||r.next(e),r.complete()}))})}function nt(e){return e<=0?function(){return he}:g(function(t,r){var n=0;t.subscribe(new x(r,function(o){++n<=e&&(r.next(o),e<=n&&r.complete())}))})}function Mn(){return g(function(e,t){e.subscribe(new x(t,Z))})}function ne(e){return g(function(t,r){t.subscribe(new x(r,function(){return r.next(e)}))})}function ir(e,t){return t?function(r){return tt(t.pipe(nt(1),Mn()),r.pipe(ir(e)))}:te(function(r,n){return e(r,n).pipe(nt(1),ne(r))})}function Me(e,t){t===void 0&&(t=et);var r=En(e,t);return ir(function(){return r})}function Q(e,t){return t===void 0&&(t=ue),e=e!=null?e:pa,g(function(r,n){var o,i=!0;r.subscribe(new x(n,function(a){var s=t(a);(i||!e(o,s))&&(i=!1,o=s,n.next(a))}))})}function pa(e,t){return e===t}function N(e,t){return Q(function(r,n){return t?t(r[e],n[e]):r[e]===n[e]})}function V(e){return g(function(t,r){t.subscribe(r),r.add(e)})}function An(e){return e<=0?function(){return he}:g(function(t,r){var n=[];t.subscribe(new x(r,function(o){n.push(o),e<n.length&&n.shift()},void 0,function(){var o,i;try{for(var a=ce(n),s=a.next();!s.done;s=a.next()){var c=s.value;r.next(c)}}catch(u){o={error:u}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}r.complete()},function(){n=null}))})}function Ln(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Lt(e,Infinity);return e=_e(e),g(function(o,i){We(n)(be(j([o],H(e)),r)).subscribe(i)})}function Ct(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Ln.apply(void 0,j([],H(e)))}function ot(e){return g(function(t,r){var n=!1,o=null;t.subscribe(new x(r,function(a){n=!0,o=a}));var i=function(){if(n){n=!1;var a=o;o=null,r.next(a)}};e.subscribe(new x(r,i,void 0,Z))})}function Hn(e,t){return g(On(e,t,arguments.length>=2,!0))}function ie(e){e=e||{};var t=e.connector,r=t===void 0?function(){return new _}:t,n=e.resetOnComplete,o=n===void 0?!0:n,i=e.resetOnError,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s,u=null,l=null,m=0,f=!1,h=!1,y=function(){u=l=null,f=h=!1};return g(function(b,z){return m++,l=l!=null?l:r(),l.subscribe(z),u||(u=Se(b).subscribe({next:function(P){return l.next(P)},error:function(P){h=!0;var C=l;a&&y(),C.error(P)},complete:function(){f=!0;var P=l;o&&y(),P.complete()}})),function(){if(m--,c&&!m&&!h&&!f){var P=u;y(),P==null||P.unsubscribe()}}})}function re(e,t,r){var n,o,i,a=!1;return e&&typeof e=="object"?(i=(n=e.bufferSize)!==null&&n!==void 0?n:Infinity,t=(o=e.windowTime)!==null&&o!==void 0?o:Infinity,a=!!e.refCount,r=e.scheduler):i=e!=null?e:Infinity,ie({connector:function(){return new yt(i,t,r)},resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:a})}function ar(e){return L(function(t,r){return e<=r})}function Cn(e){return g(function(t,r){var n=!1,o=new x(r,function(){o==null||o.unsubscribe(),n=!0},void 0,Z);W(e).subscribe(o),t.subscribe(new x(r,function(i){return n&&r.next(i)}))})}function D(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e);return g(function(n,o){(r?tt(e,n,r):tt(e,n)).subscribe(o)})}function E(e,t){return g(function(r,n){var o=null,i=0,a=!1,s=function(){return a&&!o&&n.complete()};r.subscribe(new x(n,function(c){o==null||o.unsubscribe();var u=0,l=i++;W(e(c,l)).subscribe(o=new x(n,function(m){return n.next(t?t(c,m,l,u++):m)},void 0,function(){o=null,s()}))},void 0,function(){a=!0,s()}))})}function kn(e,t){return S(t)?E(function(){return e},t):E(function(){return e})}function jn(e){return g(function(t,r){W(e).subscribe(new x(r,function(){return r.complete()},void 0,Z)),!r.closed&&t.subscribe(r)})}function Fn(e,t){return t===void 0&&(t=!1),g(function(r,n){var o=0;r.subscribe(new x(n,function(i){var a=e(i,o++);(a||t)&&n.next(i),!a&&n.complete()}))})}function k(e,t,r){var n=S(e)||t||r?{next:e,error:t,complete:r}:e;return n?g(function(o,i){o.subscribe(new x(i,function(a){var s;(s=n.next)===null||s===void 0||s.call(n,a),i.next(a)},function(a){var s;(s=n.error)===null||s===void 0||s.call(n,a),i.error(a)},function(){var a;(a=n.complete)===null||a===void 0||a.call(n),i.complete()}))}):ue}var ma={leading:!0,trailing:!1};function In(e,t){var r=t===void 0?ma:t,n=r.leading,o=r.trailing;return g(function(i,a){var s=!1,c=null,u=null,l=!1,m=function(){u==null||u.unsubscribe(),u=null,o&&(y(),l&&a.complete())},f=function(){u=null,l&&a.complete()},h=function(b){return u=W(e(b)).subscribe(new x(a,m,void 0,f))},y=function(){if(s){s=!1;var b=c;c=null,a.next(b),!l&&h(b)}};i.subscribe(new x(a,function(b){s=!0,c=b,!(u&&!u.closed)&&(n?y():h(b))},void 0,function(){l=!0,!(o&&s&&u&&!u.closed)&&a.complete()}))})}function ge(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e);return g(function(n,o){for(var i=e.length,a=new Array(i),s=e.map(function(){return!1}),c=!1,u=function(m){W(e[m]).subscribe(new x(o,function(f){a[m]=f,!c&&!s[m]&&(s[m]=!0,(c=s.every(ue))&&(s=null))},void 0,Z))},l=0;l<i;l++)u(l);n.subscribe(new x(o,function(m){if(c){var f=j([m],H(a));o.next(r?r.apply(void 0,j([],H(f))):f)}}))})}function Rn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return g(function(r,n){Ht.apply(void 0,j([r],H(e))).subscribe(n)})}function Pn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Rn.apply(void 0,j([],H(e)))}function $n(){let e=new yt;return O(document,"DOMContentLoaded").pipe(ne(document)).subscribe(e),e}function ae(e,t=document){return t.querySelector(e)||void 0}function pe(e,t=document){let r=ae(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ke(){return document.activeElement instanceof HTMLElement?document.activeElement:void 0}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function Qe(e){return document.createElement(e)}function je(e,...t){e.replaceWith(...t)}function Ae(e,t=!0){t?e.focus():e.blur()}function Vn(e){return R(O(e,"focus"),O(e,"blur")).pipe(p(({type:t})=>t==="focus"),D(e===ke()))}var Dn=new _,da=Te(()=>F(new ResizeObserver(e=>{for(let t of e)Dn.next(t)}))).pipe(E(e=>ee.pipe(D(e)).pipe(V(()=>e.disconnect()))),re(1));function Le(e){return{width:e.offsetWidth,height:e.offsetHeight}}function kt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function He(e){return da.pipe(k(t=>t.observe(e)),E(t=>Dn.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),p(()=>Le(e)))),D(Le(e)))}function Un(e){return{x:e.scrollLeft,y:e.scrollTop}}function ha(e){return R(O(e,"scroll"),O(window,"resize")).pipe(p(()=>Un(e)),D(Un(e)))}function Nn(e,t=16){return ha(e).pipe(p(({y:r})=>{let n=Le(e),o=kt(e);return r>=o.height-n.height-t}),Q())}function Wn(e){if(e instanceof HTMLInputElement)e.select();else throw new Error("Not implemented")}var jt={drawer:pe("[data-md-toggle=drawer]"),search:pe("[data-md-toggle=search]")};function zn(e){return jt[e].checked}function Fe(e,t){jt[e].checked!==t&&jt[e].click()}function Ft(e){let t=jt[e];return O(t,"change").pipe(p(()=>t.checked),D(t.checked))}function ba(e){switch(e.tagName){case"INPUT":case"SELECT":case"TEXTAREA":return!0;default:return e.isContentEditable}}function Qn(){return O(window,"keydown").pipe(L(e=>!(e.metaKey||e.ctrlKey)),p(e=>({mode:zn("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),L(({mode:e})=>{if(e==="global"){let t=ke();if(typeof t!="undefined")return!ba(t)}return!0}),ie())}function qn(){return new URL(location.href)}function Kn(e){location.href=e.href}function Jn(){return new _}function Yn(){return location.hash.substring(1)}function Bn(e){let t=Qe("a");t.href=e,t.addEventListener("click",r=>r.stopPropagation()),t.click()}function va(){return O(window,"hashchange").pipe(p(Yn),D(Yn()),L(e=>e.length>0),ie())}function Gn(){return va().pipe(E(e=>F(ae(`[id="${e}"]`))))}function qe(e){let t=matchMedia(e);return O(t,"change").pipe(p(r=>r.matches),D(t.matches))}function Xn(){return R(qe("print").pipe(L(Boolean)),O(window,"beforeprint")).pipe(ne(void 0))}function sr(e,t){return e.pipe(E(r=>r?t():ee))}function It(e,t={credentials:"same-origin"}){return Se(fetch(`${e}`,t)).pipe(L(r=>r.status===200))}function ye(e,t){return It(e,t).pipe(E(r=>r.json()),re(1))}function Zn(e,t){let r=new DOMParser;return It(e,t).pipe(E(n=>n.text()),p(n=>r.parseFromString(n,"text/xml")),re(1))}function eo(){return{x:Math.max(0,pageXOffset),y:Math.max(0,pageYOffset)}}function cr({x:e,y:t}){window.scrollTo(e||0,t||0)}function to(){return R(O(window,"scroll",{passive:!0}),O(window,"resize",{passive:!0})).pipe(p(eo),D(eo()))}function ro(){return{width:innerWidth,height:innerHeight}}function no(){return O(window,"resize",{passive:!0}).pipe(p(ro),D(ro()))}function oo(){return B([to(),no()]).pipe(p(([e,t])=>({offset:e,size:t})),re(1))}function Rt(e,{viewport$:t,header$:r}){let n=t.pipe(N("size")),o=B([n,r]).pipe(p(()=>({x:e.offsetLeft,y:e.offsetTop})));return B([r,t,o]).pipe(p(([{height:i},{offset:a,size:s},{x:c,y:u}])=>({offset:{x:a.x-c,y:a.y-u+i},size:s})))}function io(e,{tx$:t}){let r=O(e,"message").pipe(p(({data:n})=>n));return t.pipe(In(()=>r,{leading:!0,trailing:!0}),k(n=>e.postMessage(n)),kn(r),ie())}var ga=pe("#__config"),Ke=JSON.parse(ga.textContent);Ke.base=new URL(Ke.base,qn()).toString().replace(/\/$/,"");function se(){return Ke}function Pt(e){return Ke.features.includes(e)}function G(e,t){return typeof t!="undefined"?Ke.translations[e].replace("#",t.toString()):Ke.translations[e]}function Ce(e,t=document){return pe(`[data-md-component=${e}]`,t)}function me(e,t=document){return q(`[data-md-component=${e}]`,t)}var Wo=Be(lr());function ao(e,t=0){e.setAttribute("tabindex",t.toString())}function so(e){e.removeAttribute("tabindex")}function co(e,t){e.setAttribute("data-md-state","lock"),e.style.top=`-${t}px`}function uo(e){let t=-1*parseInt(e.style.top,10);e.removeAttribute("data-md-state"),e.style.top="",t&&window.scrollTo(0,t)}function lo(e,t){e.setAttribute("data-md-state",t)}function fo(e){e.removeAttribute("data-md-state")}function po(e,t){e.classList.toggle("md-nav__link--active",t)}function mo(e){e.classList.remove("md-nav__link--active")}function ho(e,t){e.firstElementChild.innerHTML=t}function bo(e,t){e.setAttribute("data-md-state",t)}function vo(e){e.removeAttribute("data-md-state")}function go(e,t){e.setAttribute("data-md-state",t)}function yo(e){e.removeAttribute("data-md-state")}function xo(e,t){e.setAttribute("data-md-state",t)}function So(e){e.removeAttribute("data-md-state")}function wo(e,t){e.placeholder=t}function Eo(e){e.placeholder=G("search.placeholder")}function Oo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oo(e,r)}function U(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="boolean"?n.setAttribute(o,t[o]):t[o]&&n.setAttribute(o,"");for(let o of r)Oo(n,o);return n}function To(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function $t(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _o(e,t){switch(t){case 0:e.textContent=G("search.result.none");break;case 1:e.textContent=G("search.result.one");break;default:e.textContent=G("search.result.other",$t(t))}}function Mo(e){e.textContent=G("search.result.placeholder")}function Ao(e,t){e.appendChild(t)}function Lo(e){e.innerHTML=""}function Ho(e,t){e.style.top=`${t}px`}function Co(e){e.style.top=""}function ko(e,t){let r=e.firstElementChild;r.style.height=`${t-2*r.offsetTop}px`}function jo(e){let t=e.firstElementChild;t.style.height=""}function Fo(e,t){e.lastElementChild.appendChild(t)}function Io(e,t){e.lastElementChild.setAttribute("data-md-state",t)}function Ro(e,t){e.setAttribute("data-md-state",t)}function fr(e){e.removeAttribute("data-md-state")}function Po(e,t){e.setAttribute("data-md-state",t)}function pr(e){e.removeAttribute("data-md-state")}function $o(e){return U("button",{class:"md-clipboard md-icon",title:G("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ie;(function(e){e[e.TEASER=1]="TEASER",e[e.PARENT=2]="PARENT"})(Ie||(Ie={}));function mr(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).map(a=>[U("del",null,a)," "]).flat().slice(0,-1),i=e.location;return U("a",{href:i,class:"md-search-result__link",tabIndex:-1},U("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&U("div",{class:"md-search-result__icon md-icon"}),U("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&U("p",{class:"md-search-result__teaser"},To(e.text,320)),n>0&&o.length>0&&U("p",{class:"md-search-result__terms"},G("search.result.term.missing"),": ",o)))}function Vo(e){let t=e[0].score,r=[...e],n=r.findIndex(u=>!u.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(u=>u.score<t);i===-1&&(i=r.length);let a=r.slice(0,i),s=r.slice(i),c=[mr(o,2|+(!n&&i===0)),...a.map(u=>mr(u,1)),...s.length?[U("details",{class:"md-search-result__more"},U("summary",{tabIndex:-1},s.length>0&&s.length===1?G("search.result.more.one"):G("search.result.more.other",s.length)),s.map(u=>mr(u,1)))]:[]];return U("li",{class:"md-search-result__item"},c)}function Do(e){return U("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>U("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?$t(r):r)))}function Uo(e){return U("div",{class:"md-typeset__scrollwrap"},U("div",{class:"md-typeset__table"},e))}function ya(e){let t=se(),r=new URL(`${e.version}/`,t.base);return U("li",{class:"md-version__item"},U("a",{href:r.toString(),class:"md-version__link"},e.title))}function No(e){let t=se(),[,r]=t.base.match(/([^/]+)\/?$/),n=e.find(({version:o,aliases:i})=>o===r||i.includes(r))||e[0];return U("div",{class:"md-version"},U("span",{class:"md-version__current"},n.title),U("ul",{class:"md-version__list"},e.map(ya)))}var xa=0;function Sa(e,{viewport$:t}){let r=F(e).pipe(E(n=>{let o=n.closest("[data-tabs]");return o instanceof HTMLElement?R(...q("input",o).map(i=>O(i,"change"))):ee}));return R(t.pipe(N("size")),r).pipe(p(()=>{let n=Le(e);return{scroll:kt(e).width>n.width}}),N("scroll"))}function zo(e,t){let r=new _;if(r.pipe(ge(qe("(hover)"))).subscribe(([{scroll:n},o])=>{n&&o?ao(e):so(e)}),Wo.default.isSupported()){let n=e.closest("pre");n.id=`__code_${xa++}`,n.insertBefore($o(n.id),e)}return Sa(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function wa(e,{target$:t,print$:r}){return t.pipe(p(n=>n.closest("details:not([open])")),L(n=>e===n),Ct(r),ne(e))}function Qo(e,t){let r=new _;return r.subscribe(()=>{e.setAttribute("open",""),e.scrollIntoView()}),wa(e,t).pipe(k(r),V(()=>r.complete()),ne({ref:e}))}var qo=Qe("table");function Ko(e){return je(e,qo),je(qo,Uo(e)),F({ref:e})}function Jo(e,{target$:t,viewport$:r,print$:n}){return R(...q("pre > code",e).map(o=>zo(o,{viewport$:r})),...q("table:not([class])",e).map(o=>Ko(o)),...q("details",e).map(o=>Qo(o,{target$:t,print$:n})))}function Ea(e,{alert$:t}){return t.pipe(E(r=>R(F(!0),F(!1).pipe(Me(2e3))).pipe(p(n=>({message:r,open:n})))))}function Yo(e,t){let r=new _;return r.pipe(Y(J)).subscribe(({message:n,open:o})=>{ho(e,n),o?bo(e,"open"):vo(e)}),Ea(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Oa({viewport$:e}){if(!Pt("header.autohide"))return F(!1);let t=e.pipe(p(({offset:{y:o}})=>o),ve(2,1),p(([o,i])=>[o<i,i]),N(0)),r=B([e,t]).pipe(L(([{offset:o},[,i]])=>Math.abs(i-o.y)>100),p(([,[o]])=>o),Q()),n=Ft("search");return B([e,n]).pipe(p(([{offset:o},i])=>o.y>400&&!i),Q(),E(o=>o?r:F(!1)),D(!1))}function Bo(e,t){return Te(()=>{let r=getComputedStyle(e);return F(r.position==="sticky"||r.position==="-webkit-sticky")}).pipe(or(He(e),Oa(t)),p(([r,{height:n},o])=>({height:r?n:0,sticky:r,hidden:o})),Q((r,n)=>r.sticky===n.sticky&&r.height===n.height&&r.hidden===n.hidden),re(1))}function Go(e,{header$:t,main$:r}){let n=new _;return n.pipe(N("active"),or(t),Y(J)).subscribe(([{active:o},{hidden:i}])=>{o?go(e,i?"hidden":"shadow"):yo(e)}),r.subscribe(o=>n.next(o)),t.pipe(p(o=>$({ref:e},o)))}function Ta(e,{viewport$:t,header$:r}){return Rt(e,{header$:r,viewport$:t}).pipe(p(({offset:{y:n}})=>{let{height:o}=Le(e);return{active:n>=o}}),N("active"))}function Xo(e,t){let r=new _;r.pipe(Y(J)).subscribe(({active:o})=>{o?xo(e,"active"):So(e)});let n=ae("article h1");return typeof n=="undefined"?ee:Ta(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function Zo(e,{viewport$:t,header$:r}){let n=r.pipe(p(({height:i})=>i),Q()),o=n.pipe(E(()=>He(e).pipe(p(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),N("bottom"))));return B([n,o,t]).pipe(p(([i,{top:a,bottom:s},{offset:{y:c},size:{height:u}}])=>(u=Math.max(0,u-Math.max(0,a-c,i)-Math.max(0,u+c-s)),{offset:a-i,height:u,active:a-i<=c})),Q((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function _a(e){let t=localStorage.getItem(__prefix("__palette")),r=JSON.parse(t)||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},n=F(...e).pipe(te(o=>O(o,"change").pipe(ne(o))),D(e[Math.max(0,r.index)]),p(o=>({index:e.indexOf(o),color:{scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),re(1));return n.subscribe(o=>{localStorage.setItem(__prefix("__palette"),JSON.stringify(o))}),n}function ei(e){let t=new _;t.subscribe(n=>{for(let[o,i]of Object.entries(n.color))typeof i=="string"&&document.body.setAttribute(`data-md-color-${o}`,i);for(let o=0;o<r.length;o++){let i=r[o].nextElementSibling;i.hidden=n.index!==o}});let r=q("input",e);return _a(r).pipe(k(t),V(()=>t.complete()),p(n=>$({ref:e},n)))}var dr=Be(lr());function ti({alert$:e}){dr.default.isSupported()&&new M(t=>{new dr.default("[data-clipboard-target], [data-clipboard-text]").on("success",r=>t.next(r))}).subscribe(()=>e.next(G("clipboard.copied")))}function Ma(e){if(e.length<2)return e;let[t,r]=e.sort((i,a)=>i.length-a.length).map(i=>i.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;let o=se();return e.map(i=>i.replace(t.slice(0,n),`${o.base}/`))}function ri({document$:e,location$:t,viewport$:r}){let n=se();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",O(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=ae("link[rel=icon]");typeof o!="undefined"&&(o.href=o.href);let i=Zn(`${n.base}/sitemap.xml`).pipe(p(u=>Ma(q("loc",u).map(l=>l.textContent))),E(u=>O(document.body,"click").pipe(L(l=>!l.metaKey&&!l.ctrlKey),E(l=>{if(l.target instanceof Element){let m=l.target.closest("a");if(m&&!m.target&&u.includes(m.href))return l.preventDefault(),F({url:new URL(m.href)})}return ee}))),ie()),a=O(window,"popstate").pipe(L(u=>u.state!==null),p(u=>({url:new URL(location.href),offset:u.state})),ie());R(i,a).pipe(Q((u,l)=>u.url.href===l.url.href),p(({url:u})=>u)).subscribe(t);let s=t.pipe(N("pathname"),E(u=>It(u.href).pipe(rt(()=>(Kn(u),ee)))),ie());i.pipe(ot(s)).subscribe(({url:u})=>{history.pushState({},"",`${u}`)});let c=new DOMParser;s.pipe(E(u=>u.text()),p(u=>c.parseFromString(u,"text/html"))).subscribe(e),R(i,a).pipe(ot(e)).subscribe(({url:u,offset:l})=>{u.hash&&!l?Bn(u.hash):cr(l||{y:0})}),e.pipe(ar(1)).subscribe(u=>{for(let l of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=logo], .md-logo","[data-md-component=skip]"]){let m=ae(l),f=ae(l,u);typeof m!="undefined"&&typeof f!="undefined"&&je(m,f)}}),e.pipe(ar(1),p(()=>Ce("container")),E(u=>F(...q("script",u))),Tn(u=>{let l=Qe("script");if(u.src){for(let m of u.getAttributeNames())l.setAttribute(m,u.getAttribute(m));return je(u,l),new M(m=>{l.onload=()=>m.complete()})}else return l.textContent=u.textContent,je(u,l),he})).subscribe(),r.pipe(Cn(i),_n(250),N("offset")).subscribe(({offset:u})=>{history.replaceState(u,"")}),R(i,a).pipe(ve(2,1),L(([u,l])=>u.url.pathname===l.url.pathname),p(([,u])=>u)).subscribe(({offset:u})=>{cr(u||{y:0})})}var Ha=Be(oi());function ii(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}var we;(function(e){e[e.SETUP=0]="SETUP",e[e.READY=1]="READY",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"})(we||(we={}));function ai(e){return e.type===1}function si(e){return e.type===2}function Vt(e){return e.type===3}function Ca({config:e,docs:t,index:r}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[G("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=G("search.config.separator"));let n=G("search.config.pipeline").split(/\s*,\s*/).filter(Boolean);return{config:e,docs:t,index:r,pipeline:n}}function ci(e,t){let r=se(),n=new Worker(e),o=new _,i=io(n,{tx$:o}).pipe(p(a=>{if(Vt(a))for(let s of a.data)for(let c of s)c.location=`${r.base}/${c.location}`;return a}),ie());return Se(t).pipe(p(a=>({type:we.SETUP,data:Ca(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function ui(){let e=se();ye(new URL("versions.json",e.base)).subscribe(t=>{pe(".md-header__topic").appendChild(No(t))})}function ka(e){let t=(__search==null?void 0:__search.transform)||ii,r=Vn(e),n=R(O(e,"keyup"),O(e,"focus").pipe(Me(1))).pipe(p(()=>t(e.value)),Q());return B([n,r]).pipe(p(([o,i])=>({value:o,focus:i})))}function li(e,{tx$:t}){let r=new _;return r.pipe(N("value"),p(({value:n})=>({type:we.QUERY,data:n}))).subscribe(t.next.bind(t)),r.pipe(N("focus")).subscribe(({focus:n})=>{n?(Fe("search",n),wo(e,"")):Eo(e)}),O(e.form,"reset").pipe(jn(r.pipe(An(1)))).subscribe(()=>Ae(e)),ka(e).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function fi(e,{rx$:t},{query$:r}){let n=new _,o=Nn(e.parentElement).pipe(L(Boolean)),i=pe(":scope > :first-child",e);n.pipe(Y(J),ge(r)).subscribe(([{data:c},{value:u}])=>{u?_o(i,c.length):Mo(i)});let a=pe(":scope > :last-child",e);return n.pipe(Y(J),k(()=>Lo(a)),E(({data:c})=>R(F(...c.slice(0,10)),F(...c.slice(10)).pipe(ve(4),Pn(o),E(([u])=>F(...u)))))).subscribe(c=>{Ao(a,Vo(c))}),t.pipe(L(Vt),p(({data:c})=>({data:c})),D({data:[]})).pipe(k(n),V(()=>n.complete()),p(c=>$({ref:e},c)))}function pi(e,{index$:t,keyboard$:r}){let n=se(),o=ci(n.search,t),i=Ce("search-query",e),a=Ce("search-result",e),{tx$:s,rx$:c}=o;s.pipe(L(si),ot(c.pipe(L(ai))),nt(1)).subscribe(s.next.bind(s)),r.pipe(L(({mode:l})=>l==="search")).subscribe(l=>{let m=ke();switch(l.type){case"Enter":m===i&&l.claim();break;case"Escape":case"Tab":Fe("search",!1),Ae(i,!1);break;case"ArrowUp":case"ArrowDown":if(typeof m=="undefined")Ae(i);else{let f=[i,...q(":not(details) > [href], summary, details[open] [href]",a)],h=Math.max(0,(Math.max(0,f.indexOf(m))+f.length+(l.type==="ArrowUp"?-1:1))%f.length);Ae(f[h])}l.claim();break;default:i!==ke()&&Ae(i)}}),r.pipe(L(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":Ae(i),Wn(i),l.claim();break}});let u=li(i,o);return R(u,fi(a,o,{query$:u}))}function ja(e,{viewport$:t,main$:r}){let n=e.parentElement.offsetTop-e.parentElement.parentElement.offsetTop;return B([r,t]).pipe(p(([{offset:o,height:i},{offset:{y:a}}])=>(i=i+Math.min(n,Math.max(0,a-o))-n,{height:i,locked:a>=o+n})),Q((o,i)=>o.height===i.height&&o.locked===i.locked))}function hr(e,n){var{header$:t}=n,r=wr(n,["header$"]);let o=new _;return o.pipe(Y(J),ge(t)).subscribe({next([{height:i},{height:a}]){ko(e,i),Ho(e,a)},complete(){Co(e),jo(e)}}),ja(e,r).pipe(k(o),V(()=>o.complete()),p(i=>$({ref:e},i)))}function mi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ht(ye(`${r}/releases/latest`).pipe(p(n=>({version:n.tag_name})),ze({})),ye(r).pipe(p(n=>({stars:n.stargazers_count,forks:n.forks_count})),ze({}))).pipe(p(([n,o])=>$($({},n),o)))}else{let r=`https://api.github.com/repos/${e}`;return ye(r).pipe(p(n=>({repositories:n.public_repos})),ze({}))}}function di(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return ye(r).pipe(p(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ze({}))}function hi(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return mi(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return di(o,i);default:return ee}}var Fa;function Ia(e){return Fa||(Fa=Te(()=>{let t=sessionStorage.getItem(__prefix("__source"));if(t)return F(JSON.parse(t));{let r=hi(e.href);return r.subscribe(n=>{try{sessionStorage.setItem(__prefix("__source"),JSON.stringify(n))}catch(o){}}),r}}).pipe(rt(()=>ee),L(t=>Object.keys(t).length>0),p(t=>({facts:t})),re(1)))}function bi(e){let t=new _;return t.subscribe(({facts:r})=>{Fo(e,Do(r)),Io(e,"done")}),Ia(e).pipe(k(t),V(()=>t.complete()),p(r=>$({ref:e},r)))}function Ra(e,{viewport$:t,header$:r}){return He(document.body).pipe(E(()=>Rt(e,{header$:r,viewport$:t})),p(({offset:{y:n}})=>({hidden:n>=10})),N("hidden"))}function vi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Ro(e,"hidden"):fr(e)},complete(){fr(e)}}),Ra(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Pa(e,{viewport$:t,header$:r}){let n=new Map;for(let a of e){let s=decodeURIComponent(a.hash.substring(1)),c=ae(`[id="${s}"]`);typeof c!="undefined"&&n.set(a,c)}let o=r.pipe(p(a=>24+a.height));return He(document.body).pipe(N("height"),p(()=>{let a=[];return[...n].reduce((s,[c,u])=>{for(;a.length&&n.get(a[a.length-1]).tagName>=u.tagName;)a.pop();let l=u.offsetTop;for(;!l&&u.parentElement;)u=u.parentElement,l=u.offsetTop;return s.set([...a=[...a,c]].reverse(),l)},new Map)}),p(a=>new Map([...a].sort(([,s],[,c])=>s-c))),E(a=>B([o,t]).pipe(Hn(([s,c],[u,{offset:{y:l}}])=>{for(;c.length;){let[,m]=c[0];if(m-u<l)s=[...s,c.shift()];else break}for(;s.length;){let[,m]=s[s.length-1];if(m-u>=l)c=[s.pop(),...c];else break}return[s,c]},[[],[...a]]),Q((s,c)=>s[0]===c[0]&&s[1]===c[1])))).pipe(p(([a,s])=>({prev:a.map(([c])=>c),next:s.map(([c])=>c)})),D({prev:[],next:[]}),ve(2,1),p(([a,s])=>a.prev.length<s.prev.length?{prev:s.prev.slice(Math.max(0,a.prev.length-1),s.prev.length),next:[]}:{prev:s.prev.slice(-1),next:s.next.slice(0,s.next.length-a.next.length)}))}function gi(e,t){let r=new _;r.pipe(Y(J)).subscribe(({prev:o,next:i})=>{for(let[a]of i)mo(a),fo(a);for(let[a,[s]]of o.entries())po(s,a===o.length-1),lo(s,"blur")});let n=q("[href^=\\#]",e);return Pa(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function $a(e,{viewport$:t,main$:r}){let n=t.pipe(p(({offset:{y:i}})=>i),ve(2,1),p(([i,a])=>i>a),Q()),o=r.pipe(N("active"));return B([o,n]).pipe(p(([{active:i},a])=>({hidden:!(i&&a)})),Q((i,a)=>i.hidden===a.hidden))}function yi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Po(e,"hidden"):pr(e)},complete(){pr(e)}}),$a(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function xi({document$:e,tablet$:t}){e.pipe(E(()=>F(...q("[data-md-state=indeterminate]"))),k(r=>{r.indeterminate=!0,r.checked=!1}),te(r=>O(r,"change").pipe(Fn(()=>r.hasAttribute("data-md-state")),ne(r))),ge(t)).subscribe(([r,n])=>{r.removeAttribute("data-md-state"),n&&(r.checked=!1)})}function Va(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(E(()=>F(...q("[data-md-scrollfix]"))),k(t=>t.removeAttribute("data-md-scrollfix")),L(Va),te(t=>O(t,"touchstart").pipe(ne(t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function wi({viewport$:e,tablet$:t}){B([Ft("search"),t]).pipe(p(([r,n])=>r&&!n),E(r=>F(r).pipe(Me(r?400:100),Y(J))),ge(e)).subscribe(([r,{offset:{y:n}}])=>{r?co(document.body,n):uo(document.body)})}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var Je=$n(),br=Jn(),vr=Gn(),gr=Qn(),le=oo(),Dt=qe("(min-width: 960px)"),Ei=qe("(min-width: 1220px)"),Oi=Xn(),Ti=se(),Da=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||ye(`${Ti.base}/search/search_index.json`):ee,yr=new _;ti({alert$:yr});Pt("navigation.instant")&&ri({document$:Je,location$:br,viewport$:le});var _i;((_i=Ti.version)==null?void 0:_i.provider)==="mike"&&ui();R(br,vr).pipe(Me(125)).subscribe(()=>{Fe("drawer",!1),Fe("search",!1)});gr.pipe(L(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ae("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=ae("[href][rel=next]");typeof r!="undefined"&&r.click();break}});xi({document$:Je,tablet$:Dt});Si({document$:Je});wi({viewport$:le,tablet$:Dt});var Re=Bo(Ce("header"),{viewport$:le}),Ut=Je.pipe(p(()=>Ce("main")),E(e=>Zo(e,{viewport$:le,header$:Re})),re(1)),Ua=R(...me("dialog").map(e=>Yo(e,{alert$:yr})),...me("header").map(e=>Go(e,{viewport$:le,header$:Re,main$:Ut})),...me("palette").map(e=>ei(e)),...me("search").map(e=>pi(e,{index$:Da,keyboard$:gr})),...me("source").map(e=>bi(e))),Na=Te(()=>R(...me("content").map(e=>Jo(e,{target$:vr,viewport$:le,print$:Oi})),...me("header-title").map(e=>Xo(e,{viewport$:le,header$:Re})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?sr(Ei,()=>hr(e,{viewport$:le,header$:Re,main$:Ut})):sr(Dt,()=>hr(e,{viewport$:le,header$:Re,main$:Ut}))),...me("tabs").map(e=>vi(e,{viewport$:le,header$:Re})),...me("toc").map(e=>gi(e,{viewport$:le,header$:Re})),...me("top").map(e=>yi(e,{viewport$:le,main$:Ut})))),Mi=Je.pipe(E(()=>Na),Ct(Ua),re(1));Mi.subscribe();window.document$=Je;window.location$=br;window.target$=vr;window.keyboard$=gr;window.viewport$=le;window.tablet$=Dt;window.screen$=Ei;window.print$=Oi;window.alert$=yr;window.component$=Mi;})();
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+//# sourceMappingURL=bundle.d892486b.min.js.map
+
diff --git a/5.4/assets/javascripts/bundle.d892486b.min.js.map b/5.4/assets/javascripts/bundle.d892486b.min.js.map
new file mode 100644 (file)
index 0000000..e57c79d
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "version": 3,
+  "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/tslib/tslib.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/assets/javascripts/bundle.ts", "node_modules/tslib/modules/index.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/caughtSchedule.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/fromArray.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/switchMapTo.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/size/index.ts", "src/assets/javascripts/browser/element/offset/index.ts", "src/assets/javascripts/browser/element/selection/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/index.ts", "src/assets/javascripts/actions/_/index.ts", "src/assets/javascripts/actions/anchor/index.ts", "src/assets/javascripts/actions/dialog/index.ts", "src/assets/javascripts/actions/header/_/index.ts", "src/assets/javascripts/actions/header/title/index.ts", "src/assets/javascripts/actions/search/query/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/actions/search/result/index.ts", "src/assets/javascripts/actions/sidebar/index.ts", "src/assets/javascripts/actions/source/index.ts", "src/assets/javascripts/actions/tabs/index.ts", "src/assets/javascripts/actions/top/index.ts", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/components/palette/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/components/top/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts"],
+  "sourcesContent": ["(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (factory());\n}(this, (function () { 'use strict';\n\n  /**\n   * Applies the :focus-visible polyfill at the given scope.\n   * A scope in this case is either the top-level Document or a Shadow Root.\n   *\n   * @param {(Document|ShadowRoot)} scope\n   * @see https://github.com/WICG/focus-visible\n   */\n  function applyFocusVisiblePolyfill(scope) {\n    var hadKeyboardEvent = true;\n    var hadFocusVisibleRecently = false;\n    var hadFocusVisibleRecentlyTimeout = null;\n\n    var inputTypesAllowlist = {\n      text: true,\n      search: true,\n      url: true,\n      tel: true,\n      email: true,\n      password: true,\n      number: true,\n      date: true,\n      month: true,\n      week: true,\n      time: true,\n      datetime: true,\n      'datetime-local': true\n    };\n\n    /**\n     * Helper function for legacy browsers and iframes which sometimes focus\n     * elements like document, body, and non-interactive SVG.\n     * @param {Element} el\n     */\n    function isValidFocusTarget(el) {\n      if (\n        el &&\n        el !== document &&\n        el.nodeName !== 'HTML' &&\n        el.nodeName !== 'BODY' &&\n        'classList' in el &&\n        'contains' in el.classList\n      ) {\n        return true;\n      }\n      return false;\n    }\n\n    /**\n     * Computes whether the given element should automatically trigger the\n     * `focus-visible` class being added, i.e. whether it should always match\n     * `:focus-visible` when focused.\n     * @param {Element} el\n     * @return {boolean}\n     */\n    function focusTriggersKeyboardModality(el) {\n      var type = el.type;\n      var tagName = el.tagName;\n\n      if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n        return true;\n      }\n\n      if (tagName === 'TEXTAREA' && !el.readOnly) {\n        return true;\n      }\n\n      if (el.isContentEditable) {\n        return true;\n      }\n\n      return false;\n    }\n\n    /**\n     * Add the `focus-visible` class to the given element if it was not added by\n     * the author.\n     * @param {Element} el\n     */\n    function addFocusVisibleClass(el) {\n      if (el.classList.contains('focus-visible')) {\n        return;\n      }\n      el.classList.add('focus-visible');\n      el.setAttribute('data-focus-visible-added', '');\n    }\n\n    /**\n     * Remove the `focus-visible` class from the given element if it was not\n     * originally added by the author.\n     * @param {Element} el\n     */\n    function removeFocusVisibleClass(el) {\n      if (!el.hasAttribute('data-focus-visible-added')) {\n        return;\n      }\n      el.classList.remove('focus-visible');\n      el.removeAttribute('data-focus-visible-added');\n    }\n\n    /**\n     * If the most recent user interaction was via the keyboard;\n     * and the key press did not include a meta, alt/option, or control key;\n     * then the modality is keyboard. Otherwise, the modality is not keyboard.\n     * Apply `focus-visible` to any current active element and keep track\n     * of our keyboard modality state with `hadKeyboardEvent`.\n     * @param {KeyboardEvent} e\n     */\n    function onKeyDown(e) {\n      if (e.metaKey || e.altKey || e.ctrlKey) {\n        return;\n      }\n\n      if (isValidFocusTarget(scope.activeElement)) {\n        addFocusVisibleClass(scope.activeElement);\n      }\n\n      hadKeyboardEvent = true;\n    }\n\n    /**\n     * If at any point a user clicks with a pointing device, ensure that we change\n     * the modality away from keyboard.\n     * This avoids the situation where a user presses a key on an already focused\n     * element, and then clicks on a different element, focusing it with a\n     * pointing device, while we still think we're in keyboard modality.\n     * @param {Event} e\n     */\n    function onPointerDown(e) {\n      hadKeyboardEvent = false;\n    }\n\n    /**\n     * On `focus`, add the `focus-visible` class to the target if:\n     * - the target received focus as a result of keyboard navigation, or\n     * - the event target is an element that will likely require interaction\n     *   via the keyboard (e.g. a text box)\n     * @param {Event} e\n     */\n    function onFocus(e) {\n      // Prevent IE from focusing the document or HTML element.\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n        addFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * On `blur`, remove the `focus-visible` class from the target.\n     * @param {Event} e\n     */\n    function onBlur(e) {\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (\n        e.target.classList.contains('focus-visible') ||\n        e.target.hasAttribute('data-focus-visible-added')\n      ) {\n        // To detect a tab/window switch, we look for a blur event followed\n        // rapidly by a visibility change.\n        // If we don't see a visibility change within 100ms, it's probably a\n        // regular focus change.\n        hadFocusVisibleRecently = true;\n        window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n        hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n          hadFocusVisibleRecently = false;\n        }, 100);\n        removeFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * If the user changes tabs, keep track of whether or not the previously\n     * focused element had .focus-visible.\n     * @param {Event} e\n     */\n    function onVisibilityChange(e) {\n      if (document.visibilityState === 'hidden') {\n        // If the tab becomes active again, the browser will handle calling focus\n        // on the element (Safari actually calls it twice).\n        // If this tab change caused a blur on an element with focus-visible,\n        // re-apply the class when the user switches back to the tab.\n        if (hadFocusVisibleRecently) {\n          hadKeyboardEvent = true;\n        }\n        addInitialPointerMoveListeners();\n      }\n    }\n\n    /**\n     * Add a group of listeners to detect usage of any pointing devices.\n     * These listeners will be added when the polyfill first loads, and anytime\n     * the window is blurred, so that they are active when the window regains\n     * focus.\n     */\n    function addInitialPointerMoveListeners() {\n      document.addEventListener('mousemove', onInitialPointerMove);\n      document.addEventListener('mousedown', onInitialPointerMove);\n      document.addEventListener('mouseup', onInitialPointerMove);\n      document.addEventListener('pointermove', onInitialPointerMove);\n      document.addEventListener('pointerdown', onInitialPointerMove);\n      document.addEventListener('pointerup', onInitialPointerMove);\n      document.addEventListener('touchmove', onInitialPointerMove);\n      document.addEventListener('touchstart', onInitialPointerMove);\n      document.addEventListener('touchend', onInitialPointerMove);\n    }\n\n    function removeInitialPointerMoveListeners() {\n      document.removeEventListener('mousemove', onInitialPointerMove);\n      document.removeEventListener('mousedown', onInitialPointerMove);\n      document.removeEventListener('mouseup', onInitialPointerMove);\n      document.removeEventListener('pointermove', onInitialPointerMove);\n      document.removeEventListener('pointerdown', onInitialPointerMove);\n      document.removeEventListener('pointerup', onInitialPointerMove);\n      document.removeEventListener('touchmove', onInitialPointerMove);\n      document.removeEventListener('touchstart', onInitialPointerMove);\n      document.removeEventListener('touchend', onInitialPointerMove);\n    }\n\n    /**\n     * When the polfyill first loads, assume the user is in keyboard modality.\n     * If any event is received from a pointing device (e.g. mouse, pointer,\n     * touch), turn off keyboard modality.\n     * This accounts for situations where focus enters the page from the URL bar.\n     * @param {Event} e\n     */\n    function onInitialPointerMove(e) {\n      // Work around a Safari quirk that fires a mousemove on <html> whenever the\n      // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n      if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n        return;\n      }\n\n      hadKeyboardEvent = false;\n      removeInitialPointerMoveListeners();\n    }\n\n    // For some kinds of state, we are interested in changes at the global scope\n    // only. For example, global pointer input, global key presses and global\n    // visibility change should affect the state at every scope:\n    document.addEventListener('keydown', onKeyDown, true);\n    document.addEventListener('mousedown', onPointerDown, true);\n    document.addEventListener('pointerdown', onPointerDown, true);\n    document.addEventListener('touchstart', onPointerDown, true);\n    document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n    addInitialPointerMoveListeners();\n\n    // For focus and blur, we specifically care about state changes in the local\n    // scope. This is because focus / blur events that originate from within a\n    // shadow root are not re-dispatched from the host element if it was already\n    // the active element in its own scope:\n    scope.addEventListener('focus', onFocus, true);\n    scope.addEventListener('blur', onBlur, true);\n\n    // We detect that a node is a ShadowRoot by ensuring that it is a\n    // DocumentFragment and also has a host property. This check covers native\n    // implementation and polyfill implementation transparently. If we only cared\n    // about the native implementation, we could just check if the scope was\n    // an instance of a ShadowRoot.\n    if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n      // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n      // have a root element to add a class to. So, we add this attribute to the\n      // host element instead:\n      scope.host.setAttribute('data-js-focus-visible', '');\n    } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n      document.documentElement.classList.add('js-focus-visible');\n      document.documentElement.setAttribute('data-js-focus-visible', '');\n    }\n  }\n\n  // It is important to wrap all references to global window and document in\n  // these checks to support server-side rendering use cases\n  // @see https://github.com/WICG/focus-visible/issues/199\n  if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n    // Make the polyfill helper globally available. This can be used as a signal\n    // to interested libraries that wish to coordinate with the polyfill for e.g.,\n    // applying the polyfill to a shadow root:\n    window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n    // Notify interested libraries of the polyfill's presence, in case the\n    // polyfill was loaded lazily:\n    var event;\n\n    try {\n      event = new CustomEvent('focus-visible-polyfill-ready');\n    } catch (error) {\n      // IE11 does not support using CustomEvent as a constructor directly:\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n    }\n\n    window.dispatchEvent(event);\n  }\n\n  if (typeof document !== 'undefined') {\n    // Apply the polyfill to the global document, so that no JavaScript\n    // coordination is required to use the polyfill in the top-level document:\n    applyFocusVisiblePolyfill(document);\n  }\n\n})));\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global global, define, System, Reflect, Promise */\r\nvar __extends;\r\nvar __assign;\r\nvar __rest;\r\nvar __decorate;\r\nvar __param;\r\nvar __metadata;\r\nvar __awaiter;\r\nvar __generator;\r\nvar __exportStar;\r\nvar __values;\r\nvar __read;\r\nvar __spread;\r\nvar __spreadArrays;\r\nvar __spreadArray;\r\nvar __await;\r\nvar __asyncGenerator;\r\nvar __asyncDelegator;\r\nvar __asyncValues;\r\nvar __makeTemplateObject;\r\nvar __importStar;\r\nvar __importDefault;\r\nvar __classPrivateFieldGet;\r\nvar __classPrivateFieldSet;\r\nvar __createBinding;\r\n(function (factory) {\r\n    var root = typeof global === \"object\" ? global : typeof self === \"object\" ? self : typeof this === \"object\" ? this : {};\r\n    if (typeof define === \"function\" && define.amd) {\r\n        define(\"tslib\", [\"exports\"], function (exports) { factory(createExporter(root, createExporter(exports))); });\r\n    }\r\n    else if (typeof module === \"object\" && typeof module.exports === \"object\") {\r\n        factory(createExporter(root, createExporter(module.exports)));\r\n    }\r\n    else {\r\n        factory(createExporter(root));\r\n    }\r\n    function createExporter(exports, previous) {\r\n        if (exports !== root) {\r\n            if (typeof Object.create === \"function\") {\r\n                Object.defineProperty(exports, \"__esModule\", { value: true });\r\n            }\r\n            else {\r\n                exports.__esModule = true;\r\n            }\r\n        }\r\n        return function (id, v) { return exports[id] = previous ? previous(id, v) : v; };\r\n    }\r\n})\r\n(function (exporter) {\r\n    var extendStatics = Object.setPrototypeOf ||\r\n        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n        function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n\r\n    __extends = function (d, b) {\r\n        if (typeof b !== \"function\" && b !== null)\r\n            throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n        extendStatics(d, b);\r\n        function __() { this.constructor = d; }\r\n        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n    };\r\n\r\n    __assign = Object.assign || function (t) {\r\n        for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n            s = arguments[i];\r\n            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n        }\r\n        return t;\r\n    };\r\n\r\n    __rest = function (s, e) {\r\n        var t = {};\r\n        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n            t[p] = s[p];\r\n        if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n            for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n                if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n                    t[p[i]] = s[p[i]];\r\n            }\r\n        return t;\r\n    };\r\n\r\n    __decorate = function (decorators, target, key, desc) {\r\n        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n        if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n        return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n    };\r\n\r\n    __param = function (paramIndex, decorator) {\r\n        return function (target, key) { decorator(target, key, paramIndex); }\r\n    };\r\n\r\n    __metadata = function (metadataKey, metadataValue) {\r\n        if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n    };\r\n\r\n    __awaiter = function (thisArg, _arguments, P, generator) {\r\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n        return new (P || (P = Promise))(function (resolve, reject) {\r\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n        });\r\n    };\r\n\r\n    __generator = function (thisArg, body) {\r\n        var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n        return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n        function verb(n) { return function (v) { return step([n, v]); }; }\r\n        function step(op) {\r\n            if (f) throw new TypeError(\"Generator is already executing.\");\r\n            while (_) try {\r\n                if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n                if (y = 0, t) op = [op[0] & 2, t.value];\r\n                switch (op[0]) {\r\n                    case 0: case 1: t = op; break;\r\n                    case 4: _.label++; return { value: op[1], done: false };\r\n                    case 5: _.label++; y = op[1]; op = [0]; continue;\r\n                    case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n                    default:\r\n                        if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n                        if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n                        if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n                        if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n                        if (t[2]) _.ops.pop();\r\n                        _.trys.pop(); continue;\r\n                }\r\n                op = body.call(thisArg, _);\r\n            } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n            if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n        }\r\n    };\r\n\r\n    __exportStar = function(m, o) {\r\n        for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n    };\r\n\r\n    __createBinding = Object.create ? (function(o, m, k, k2) {\r\n        if (k2 === undefined) k2 = k;\r\n        Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n    }) : (function(o, m, k, k2) {\r\n        if (k2 === undefined) k2 = k;\r\n        o[k2] = m[k];\r\n    });\r\n\r\n    __values = function (o) {\r\n        var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n        if (m) return m.call(o);\r\n        if (o && typeof o.length === \"number\") return {\r\n            next: function () {\r\n                if (o && i >= o.length) o = void 0;\r\n                return { value: o && o[i++], done: !o };\r\n            }\r\n        };\r\n        throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n    };\r\n\r\n    __read = function (o, n) {\r\n        var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n        if (!m) return o;\r\n        var i = m.call(o), r, ar = [], e;\r\n        try {\r\n            while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n        }\r\n        catch (error) { e = { error: error }; }\r\n        finally {\r\n            try {\r\n                if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n            }\r\n            finally { if (e) throw e.error; }\r\n        }\r\n        return ar;\r\n    };\r\n\r\n    /** @deprecated */\r\n    __spread = function () {\r\n        for (var ar = [], i = 0; i < arguments.length; i++)\r\n            ar = ar.concat(__read(arguments[i]));\r\n        return ar;\r\n    };\r\n\r\n    /** @deprecated */\r\n    __spreadArrays = function () {\r\n        for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n        for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n            for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n                r[k] = a[j];\r\n        return r;\r\n    };\r\n\r\n    __spreadArray = function (to, from) {\r\n        for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)\r\n            to[j] = from[i];\r\n        return to;\r\n    };\r\n\r\n    __await = function (v) {\r\n        return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n    };\r\n\r\n    __asyncGenerator = function (thisArg, _arguments, generator) {\r\n        if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n        var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n        return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n        function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n        function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n        function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);  }\r\n        function fulfill(value) { resume(\"next\", value); }\r\n        function reject(value) { resume(\"throw\", value); }\r\n        function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n    };\r\n\r\n    __asyncDelegator = function (o) {\r\n        var i, p;\r\n        return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n        function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n    };\r\n\r\n    __asyncValues = function (o) {\r\n        if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n        var m = o[Symbol.asyncIterator], i;\r\n        return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n        function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n        function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n    };\r\n\r\n    __makeTemplateObject = function (cooked, raw) {\r\n        if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n        return cooked;\r\n    };\r\n\r\n    var __setModuleDefault = Object.create ? (function(o, v) {\r\n        Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n    }) : function(o, v) {\r\n        o[\"default\"] = v;\r\n    };\r\n\r\n    __importStar = function (mod) {\r\n        if (mod && mod.__esModule) return mod;\r\n        var result = {};\r\n        if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n        __setModuleDefault(result, mod);\r\n        return result;\r\n    };\r\n\r\n    __importDefault = function (mod) {\r\n        return (mod && mod.__esModule) ? mod : { \"default\": mod };\r\n    };\r\n\r\n    __classPrivateFieldGet = function (receiver, privateMap) {\r\n        if (!privateMap.has(receiver)) {\r\n            throw new TypeError(\"attempted to get private field on non-instance\");\r\n        }\r\n        return privateMap.get(receiver);\r\n    };\r\n\r\n    __classPrivateFieldSet = function (receiver, privateMap, value) {\r\n        if (!privateMap.has(receiver)) {\r\n            throw new TypeError(\"attempted to set private field on non-instance\");\r\n        }\r\n        privateMap.set(receiver, value);\r\n        return value;\r\n    };\r\n\r\n    exporter(\"__extends\", __extends);\r\n    exporter(\"__assign\", __assign);\r\n    exporter(\"__rest\", __rest);\r\n    exporter(\"__decorate\", __decorate);\r\n    exporter(\"__param\", __param);\r\n    exporter(\"__metadata\", __metadata);\r\n    exporter(\"__awaiter\", __awaiter);\r\n    exporter(\"__generator\", __generator);\r\n    exporter(\"__exportStar\", __exportStar);\r\n    exporter(\"__createBinding\", __createBinding);\r\n    exporter(\"__values\", __values);\r\n    exporter(\"__read\", __read);\r\n    exporter(\"__spread\", __spread);\r\n    exporter(\"__spreadArrays\", __spreadArrays);\r\n    exporter(\"__spreadArray\", __spreadArray);\r\n    exporter(\"__await\", __await);\r\n    exporter(\"__asyncGenerator\", __asyncGenerator);\r\n    exporter(\"__asyncDelegator\", __asyncDelegator);\r\n    exporter(\"__asyncValues\", __asyncValues);\r\n    exporter(\"__makeTemplateObject\", __makeTemplateObject);\r\n    exporter(\"__importStar\", __importStar);\r\n    exporter(\"__importDefault\", __importDefault);\r\n    exporter(\"__classPrivateFieldGet\", __classPrivateFieldGet);\r\n    exporter(\"__classPrivateFieldSet\", __classPrivateFieldSet);\r\n});\r\n", "/*!\n * clipboard.js v2.0.8\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 134:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n  \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/clipboard-action.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n/**\n * Inner class which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n */\n\nvar ClipboardAction = /*#__PURE__*/function () {\n  /**\n   * @param {Object} options\n   */\n  function ClipboardAction(options) {\n    _classCallCheck(this, ClipboardAction);\n\n    this.resolveOptions(options);\n    this.initSelection();\n  }\n  /**\n   * Defines base properties passed from constructor.\n   * @param {Object} options\n   */\n\n\n  _createClass(ClipboardAction, [{\n    key: \"resolveOptions\",\n    value: function resolveOptions() {\n      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      this.action = options.action;\n      this.container = options.container;\n      this.emitter = options.emitter;\n      this.target = options.target;\n      this.text = options.text;\n      this.trigger = options.trigger;\n      this.selectedText = '';\n    }\n    /**\n     * Decides which selection strategy is going to be applied based\n     * on the existence of `text` and `target` properties.\n     */\n\n  }, {\n    key: \"initSelection\",\n    value: function initSelection() {\n      if (this.text) {\n        this.selectFake();\n      } else if (this.target) {\n        this.selectTarget();\n      }\n    }\n    /**\n     * Creates a fake textarea element, sets its value from `text` property,\n     */\n\n  }, {\n    key: \"createFakeElement\",\n    value: function createFakeElement() {\n      var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n      this.fakeElem = document.createElement('textarea'); // Prevent zooming on iOS\n\n      this.fakeElem.style.fontSize = '12pt'; // Reset box model\n\n      this.fakeElem.style.border = '0';\n      this.fakeElem.style.padding = '0';\n      this.fakeElem.style.margin = '0'; // Move element out of screen horizontally\n\n      this.fakeElem.style.position = 'absolute';\n      this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n      var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n      this.fakeElem.style.top = \"\".concat(yPosition, \"px\");\n      this.fakeElem.setAttribute('readonly', '');\n      this.fakeElem.value = this.text;\n      return this.fakeElem;\n    }\n    /**\n     * Get's the value of fakeElem,\n     * and makes a selection on it.\n     */\n\n  }, {\n    key: \"selectFake\",\n    value: function selectFake() {\n      var _this = this;\n\n      var fakeElem = this.createFakeElement();\n\n      this.fakeHandlerCallback = function () {\n        return _this.removeFake();\n      };\n\n      this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;\n      this.container.appendChild(fakeElem);\n      this.selectedText = select_default()(fakeElem);\n      this.copyText();\n      this.removeFake();\n    }\n    /**\n     * Only removes the fake element after another click event, that way\n     * a user can hit `Ctrl+C` to copy because selection still exists.\n     */\n\n  }, {\n    key: \"removeFake\",\n    value: function removeFake() {\n      if (this.fakeHandler) {\n        this.container.removeEventListener('click', this.fakeHandlerCallback);\n        this.fakeHandler = null;\n        this.fakeHandlerCallback = null;\n      }\n\n      if (this.fakeElem) {\n        this.container.removeChild(this.fakeElem);\n        this.fakeElem = null;\n      }\n    }\n    /**\n     * Selects the content from element passed on `target` property.\n     */\n\n  }, {\n    key: \"selectTarget\",\n    value: function selectTarget() {\n      this.selectedText = select_default()(this.target);\n      this.copyText();\n    }\n    /**\n     * Executes the copy operation based on the current selection.\n     */\n\n  }, {\n    key: \"copyText\",\n    value: function copyText() {\n      var succeeded;\n\n      try {\n        succeeded = document.execCommand(this.action);\n      } catch (err) {\n        succeeded = false;\n      }\n\n      this.handleResult(succeeded);\n    }\n    /**\n     * Fires an event based on the copy operation result.\n     * @param {Boolean} succeeded\n     */\n\n  }, {\n    key: \"handleResult\",\n    value: function handleResult(succeeded) {\n      this.emitter.emit(succeeded ? 'success' : 'error', {\n        action: this.action,\n        text: this.selectedText,\n        trigger: this.trigger,\n        clearSelection: this.clearSelection.bind(this)\n      });\n    }\n    /**\n     * Moves focus away from `target` and back to the trigger, removes current selection.\n     */\n\n  }, {\n    key: \"clearSelection\",\n    value: function clearSelection() {\n      if (this.trigger) {\n        this.trigger.focus();\n      }\n\n      document.activeElement.blur();\n      window.getSelection().removeAllRanges();\n    }\n    /**\n     * Sets the `action` to be performed which can be either 'copy' or 'cut'.\n     * @param {String} action\n     */\n\n  }, {\n    key: \"destroy\",\n\n    /**\n     * Destroy lifecycle.\n     */\n    value: function destroy() {\n      this.removeFake();\n    }\n  }, {\n    key: \"action\",\n    set: function set() {\n      var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';\n      this._action = action;\n\n      if (this._action !== 'copy' && this._action !== 'cut') {\n        throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n      }\n    }\n    /**\n     * Gets the `action` property.\n     * @return {String}\n     */\n    ,\n    get: function get() {\n      return this._action;\n    }\n    /**\n     * Sets the `target` property using an element\n     * that will be have its content copied.\n     * @param {Element} target\n     */\n\n  }, {\n    key: \"target\",\n    set: function set(target) {\n      if (target !== undefined) {\n        if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n          if (this.action === 'copy' && target.hasAttribute('disabled')) {\n            throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n          }\n\n          if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n            throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n          }\n\n          this._target = target;\n        } else {\n          throw new Error('Invalid \"target\" value, use a valid Element');\n        }\n      }\n    }\n    /**\n     * Gets the `target` property.\n     * @return {String|HTMLElement}\n     */\n    ,\n    get: function get() {\n      return this._target;\n    }\n  }]);\n\n  return ClipboardAction;\n}();\n\n/* harmony default export */ var clipboard_action = (ClipboardAction);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction clipboard_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) clipboard_defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n  var attribute = \"data-clipboard-\".concat(suffix);\n\n  if (!element.hasAttribute(attribute)) {\n    return;\n  }\n\n  return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n  _inherits(Clipboard, _Emitter);\n\n  var _super = _createSuper(Clipboard);\n\n  /**\n   * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n   * @param {Object} options\n   */\n  function Clipboard(trigger, options) {\n    var _this;\n\n    clipboard_classCallCheck(this, Clipboard);\n\n    _this = _super.call(this);\n\n    _this.resolveOptions(options);\n\n    _this.listenClick(trigger);\n\n    return _this;\n  }\n  /**\n   * Defines if attributes would be resolved using internal setter functions\n   * or custom functions that were passed in the constructor.\n   * @param {Object} options\n   */\n\n\n  clipboard_createClass(Clipboard, [{\n    key: \"resolveOptions\",\n    value: function resolveOptions() {\n      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n      this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n      this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n      this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n    }\n    /**\n     * Adds a click event listener to the passed trigger.\n     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n     */\n\n  }, {\n    key: \"listenClick\",\n    value: function listenClick(trigger) {\n      var _this2 = this;\n\n      this.listener = listen_default()(trigger, 'click', function (e) {\n        return _this2.onClick(e);\n      });\n    }\n    /**\n     * Defines a new `ClipboardAction` on each click event.\n     * @param {Event} e\n     */\n\n  }, {\n    key: \"onClick\",\n    value: function onClick(e) {\n      var trigger = e.delegateTarget || e.currentTarget;\n\n      if (this.clipboardAction) {\n        this.clipboardAction = null;\n      }\n\n      this.clipboardAction = new clipboard_action({\n        action: this.action(trigger),\n        target: this.target(trigger),\n        text: this.text(trigger),\n        container: this.container,\n        trigger: trigger,\n        emitter: this\n      });\n    }\n    /**\n     * Default `action` lookup function.\n     * @param {Element} trigger\n     */\n\n  }, {\n    key: \"defaultAction\",\n    value: function defaultAction(trigger) {\n      return getAttributeValue('action', trigger);\n    }\n    /**\n     * Default `target` lookup function.\n     * @param {Element} trigger\n     */\n\n  }, {\n    key: \"defaultTarget\",\n    value: function defaultTarget(trigger) {\n      var selector = getAttributeValue('target', trigger);\n\n      if (selector) {\n        return document.querySelector(selector);\n      }\n    }\n    /**\n     * Returns the support of the given action, or all actions if no action is\n     * given.\n     * @param {String} [action]\n     */\n\n  }, {\n    key: \"defaultText\",\n\n    /**\n     * Default `text` lookup function.\n     * @param {Element} trigger\n     */\n    value: function defaultText(trigger) {\n      return getAttributeValue('text', trigger);\n    }\n    /**\n     * Destroy lifecycle.\n     */\n\n  }, {\n    key: \"destroy\",\n    value: function destroy() {\n      this.listener.destroy();\n\n      if (this.clipboardAction) {\n        this.clipboardAction.destroy();\n        this.clipboardAction = null;\n      }\n    }\n  }], [{\n    key: \"isSupported\",\n    value: function isSupported() {\n      var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n      var actions = typeof action === 'string' ? [action] : action;\n      var support = !!document.queryCommandSupported;\n      actions.forEach(function (action) {\n        support = support && !!document.queryCommandSupported(action);\n      });\n      return support;\n    }\n  }]);\n\n  return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n    var proto = Element.prototype;\n\n    proto.matches = proto.matchesSelector ||\n                    proto.mozMatchesSelector ||\n                    proto.msMatchesSelector ||\n                    proto.oMatchesSelector ||\n                    proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n    while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n        if (typeof element.matches === 'function' &&\n            element.matches(selector)) {\n          return element;\n        }\n        element = element.parentNode;\n    }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n    var listenerFn = listener.apply(this, arguments);\n\n    element.addEventListener(type, listenerFn, useCapture);\n\n    return {\n        destroy: function() {\n            element.removeEventListener(type, listenerFn, useCapture);\n        }\n    }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n    // Handle the regular Element usage\n    if (typeof elements.addEventListener === 'function') {\n        return _delegate.apply(null, arguments);\n    }\n\n    // Handle Element-less usage, it defaults to global delegation\n    if (typeof type === 'function') {\n        // Use `document` as the first parameter, then apply arguments\n        // This is a short way to .unshift `arguments` without running into deoptimizations\n        return _delegate.bind(null, document).apply(null, arguments);\n    }\n\n    // Handle Selector-based usage\n    if (typeof elements === 'string') {\n        elements = document.querySelectorAll(elements);\n    }\n\n    // Handle Array-like based usage\n    return Array.prototype.map.call(elements, function (element) {\n        return _delegate(element, selector, type, callback, useCapture);\n    });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n    return function(e) {\n        e.delegateTarget = closest(e.target, selector);\n\n        if (e.delegateTarget) {\n            callback.call(element, e);\n        }\n    }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n    return value !== undefined\n        && value instanceof HTMLElement\n        && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return value !== undefined\n        && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n        && ('length' in value)\n        && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n    return typeof value === 'string'\n        || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n    if (!target && !type && !callback) {\n        throw new Error('Missing required arguments');\n    }\n\n    if (!is.string(type)) {\n        throw new TypeError('Second argument must be a String');\n    }\n\n    if (!is.fn(callback)) {\n        throw new TypeError('Third argument must be a Function');\n    }\n\n    if (is.node(target)) {\n        return listenNode(target, type, callback);\n    }\n    else if (is.nodeList(target)) {\n        return listenNodeList(target, type, callback);\n    }\n    else if (is.string(target)) {\n        return listenSelector(target, type, callback);\n    }\n    else {\n        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n    }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n    node.addEventListener(type, callback);\n\n    return {\n        destroy: function() {\n            node.removeEventListener(type, callback);\n        }\n    }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n    Array.prototype.forEach.call(nodeList, function(node) {\n        node.addEventListener(type, callback);\n    });\n\n    return {\n        destroy: function() {\n            Array.prototype.forEach.call(nodeList, function(node) {\n                node.removeEventListener(type, callback);\n            });\n        }\n    }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n    return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n    var selectedText;\n\n    if (element.nodeName === 'SELECT') {\n        element.focus();\n\n        selectedText = element.value;\n    }\n    else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n        var isReadOnly = element.hasAttribute('readonly');\n\n        if (!isReadOnly) {\n            element.setAttribute('readonly', '');\n        }\n\n        element.select();\n        element.setSelectionRange(0, element.value.length);\n\n        if (!isReadOnly) {\n            element.removeAttribute('readonly');\n        }\n\n        selectedText = element.value;\n    }\n    else {\n        if (element.hasAttribute('contenteditable')) {\n            element.focus();\n        }\n\n        var selection = window.getSelection();\n        var range = document.createRange();\n\n        range.selectNodeContents(element);\n        selection.removeAllRanges();\n        selection.addRange(range);\n\n        selectedText = selection.toString();\n    }\n\n    return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n  // Keep this empty so it's easier to inherit from\n  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n  on: function (name, callback, ctx) {\n    var e = this.e || (this.e = {});\n\n    (e[name] || (e[name] = [])).push({\n      fn: callback,\n      ctx: ctx\n    });\n\n    return this;\n  },\n\n  once: function (name, callback, ctx) {\n    var self = this;\n    function listener () {\n      self.off(name, listener);\n      callback.apply(ctx, arguments);\n    };\n\n    listener._ = callback\n    return this.on(name, listener, ctx);\n  },\n\n  emit: function (name) {\n    var data = [].slice.call(arguments, 1);\n    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n    var i = 0;\n    var len = evtArr.length;\n\n    for (i; i < len; i++) {\n      evtArr[i].fn.apply(evtArr[i].ctx, data);\n    }\n\n    return this;\n  },\n\n  off: function (name, callback) {\n    var e = this.e || (this.e = {});\n    var evts = e[name];\n    var liveEvents = [];\n\n    if (evts && callback) {\n      for (var i = 0, len = evts.length; i < len; i++) {\n        if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n          liveEvents.push(evts[i]);\n      }\n    }\n\n    // Remove event from queue to prevent memory leak\n    // Suggested by https://github.com/lazd\n    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n    (liveEvents.length)\n      ? e[name] = liveEvents\n      : delete e[name];\n\n    return this;\n  }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(134);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\nimport { NEVER, Subject, defer, merge } from \"rxjs\"\nimport {\n  delay,\n  filter,\n  map,\n  mergeWith,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n  at,\n  getElement,\n  requestJSON,\n  setToggle,\n  watchDocument,\n  watchKeyboard,\n  watchLocation,\n  watchLocationTarget,\n  watchMedia,\n  watchPrint,\n  watchViewport\n} from \"./browser\"\nimport {\n  getComponentElement,\n  getComponentElements,\n  mountBackToTop,\n  mountContent,\n  mountDialog,\n  mountHeader,\n  mountHeaderTitle,\n  mountPalette,\n  mountSearch,\n  mountSidebar,\n  mountSource,\n  mountTableOfContents,\n  mountTabs,\n  watchHeader,\n  watchMain\n} from \"./components\"\nimport {\n  SearchIndex,\n  setupClipboardJS,\n  setupInstantLoading,\n  setupVersionSelector\n} from \"./integrations\"\nimport {\n  patchIndeterminate,\n  patchScrollfix,\n  patchScrolllock\n} from \"./patches\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$   = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$   = watchMedia(\"(min-width: 960px)\")\nconst screen$   = watchMedia(\"(min-width: 1220px)\")\nconst print$    = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n  ? __search?.index || requestJSON<SearchIndex>(\n    `${config.base}/search/search_index.json`\n  )\n  : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject<string>()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n  setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n  setupVersionSelector()\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n  .pipe(\n    delay(125)\n  )\n    .subscribe(() => {\n      setToggle(\"drawer\", false)\n      setToggle(\"search\", false)\n    })\n\n/* Set up global keyboard handlers */\nkeyboard$\n  .pipe(\n    filter(({ mode }) => mode === \"global\")\n  )\n    .subscribe(key => {\n      switch (key.type) {\n\n        /* Go to previous page */\n        case \"p\":\n        case \",\":\n          const prev = getElement(\"[href][rel=prev]\")\n          if (typeof prev !== \"undefined\")\n            prev.click()\n          break\n\n        /* Go to next page */\n        case \"n\":\n        case \".\":\n          const next = getElement(\"[href][rel=next]\")\n          if (typeof next !== \"undefined\")\n            next.click()\n          break\n      }\n    })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n  .pipe(\n    map(() => getComponentElement(\"main\")),\n    switchMap(el => watchMain(el, { viewport$, header$ })),\n    shareReplay(1)\n  )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n  /* Dialog */\n  ...getComponentElements(\"dialog\")\n    .map(el => mountDialog(el, { alert$ })),\n\n  /* Header */\n  ...getComponentElements(\"header\")\n    .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n  /* Color palette */\n  ...getComponentElements(\"palette\")\n    .map(el => mountPalette(el)),\n\n  /* Search */\n  ...getComponentElements(\"search\")\n    .map(el => mountSearch(el, { index$, keyboard$ })),\n\n  /* Repository information */\n  ...getComponentElements(\"source\")\n    .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n  /* Content */\n  ...getComponentElements(\"content\")\n    .map(el => mountContent(el, { target$, viewport$, print$ })),\n\n  /* Header title */\n  ...getComponentElements(\"header-title\")\n    .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n  /* Sidebar */\n  ...getComponentElements(\"sidebar\")\n    .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n      ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n      : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n    ),\n\n  /* Navigation tabs */\n  ...getComponentElements(\"tabs\")\n    .map(el => mountTabs(el, { viewport$, header$ })),\n\n  /* Table of contents */\n  ...getComponentElements(\"toc\")\n    .map(el => mountTableOfContents(el, { viewport$, header$ })),\n\n  /* Back-to-top button */\n  ...getComponentElements(\"top\")\n    .map(el => mountBackToTop(el, { viewport$, main$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n  .pipe(\n    switchMap(() => content$),\n    mergeWith(control$),\n    shareReplay(1)\n  )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$  = document$          /* Document observable */\nwindow.location$  = location$          /* Location subject */\nwindow.target$    = target$            /* Location target observable */\nwindow.keyboard$  = keyboard$          /* Keyboard observable */\nwindow.viewport$  = viewport$          /* Viewport observable */\nwindow.tablet$    = tablet$            /* Tablet observable */\nwindow.screen$    = screen$            /* Screen observable */\nwindow.print$     = print$             /* Print mode observable */\nwindow.alert$     = alert$             /* Alert subject */\nwindow.component$ = component$         /* Component observable */\n", "import tslib from '../tslib.js';\r\nconst {\r\n    __extends,\r\n    __assign,\r\n    __rest,\r\n    __decorate,\r\n    __param,\r\n    __metadata,\r\n    __awaiter,\r\n    __generator,\r\n    __exportStar,\r\n    __createBinding,\r\n    __values,\r\n    __read,\r\n    __spread,\r\n    __spreadArrays,\r\n    __spreadArray,\r\n    __await,\r\n    __asyncGenerator,\r\n    __asyncDelegator,\r\n    __asyncValues,\r\n    __makeTemplateObject,\r\n    __importStar,\r\n    __importDefault,\r\n    __classPrivateFieldGet,\r\n    __classPrivateFieldSet,\r\n} = tslib;\r\nexport {\r\n    __extends,\r\n    __assign,\r\n    __rest,\r\n    __decorate,\r\n    __param,\r\n    __metadata,\r\n    __awaiter,\r\n    __generator,\r\n    __exportStar,\r\n    __createBinding,\r\n    __values,\r\n    __read,\r\n    __spread,\r\n    __spreadArrays,\r\n    __spreadArray,\r\n    __await,\r\n    __asyncGenerator,\r\n    __asyncDelegator,\r\n    __asyncValues,\r\n    __makeTemplateObject,\r\n    __importStar,\r\n    __importDefault,\r\n    __classPrivateFieldGet,\r\n    __classPrivateFieldSet,\r\n};\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ReplaySubject, Subject, fromEvent } from \"rxjs\"\nimport { mapTo } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject<Document> {\n  const document$ = new ReplaySubject<Document>()\n  fromEvent(document, \"DOMContentLoaded\")\n    .pipe(\n      mapTo(document)\n    )\n      .subscribe(document$)\n\n  /* Return document */\n  return document$\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getElement<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T | undefined\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T | undefined {\n  return node.querySelector<T>(selector) || undefined\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T {\n  const el = getElement<T>(selector, node)\n  if (typeof el === \"undefined\")\n    throw new ReferenceError(\n      `Missing element: expected \"${selector}\" to be present`\n    )\n  return el\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n  return document.activeElement instanceof HTMLElement\n    ? document.activeElement\n    : undefined\n}\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T[]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T[] {\n  return Array.from(node.querySelectorAll<T>(selector))\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Create an element\n *\n * @template T - Tag name type\n *\n * @param tagName - Tag name\n *\n * @returns Element\n */\nexport function createElement<T extends keyof HTMLElementTagNameMap>(\n  tagName: T\n): HTMLElementTagNameMap[T] {\n  return document.createElement(tagName)\n}\n\n/**\n * Replace an element with the given list of nodes\n *\n * @param el - Element\n * @param nodes - Replacement nodes\n */\nexport function replaceElement(\n  el: HTMLElement, ...nodes: Node[]\n): void {\n  el.replaceWith(...nodes)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element focus\n *\n * @param el - Element\n * @param value - Whether the element should be focused\n */\nexport function setElementFocus(\n  el: HTMLElement, value = true\n): void {\n  if (value)\n    el.focus()\n  else\n    el.blur()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n  el: HTMLElement\n): Observable<boolean> {\n  return merge(\n    fromEvent<FocusEvent>(el, \"focus\"),\n    fromEvent<FocusEvent>(el, \"blur\")\n  )\n    .pipe(\n      map(({ type }) => type === \"focus\"),\n      startWith(el === getActiveElement())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n  width: number                        /* Element width */\n  height: number                       /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject<ResizeObserverEntry>()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n  new ResizeObserver(entries => {\n    for (const entry of entries)\n      entry$.next(entry)\n  })\n))\n  .pipe(\n    switchMap(resize => NEVER.pipe(startWith(resize))\n      .pipe(\n        finalize(() => resize.disconnect())\n      )\n    ),\n    shareReplay(1)\n  )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.offsetWidth,\n    height: el.offsetHeight\n  }\n}\n\n/**\n * Retrieve element content size, i.e. including overflowing content\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementContentSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.scrollWidth,\n    height: el.scrollHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * Sadly, we can't use the `DOMRect` objects returned by the observer, because\n * we need the emitted values to be consistent with `getElementSize`, which will\n * return the used values (rounded) and not actual values (unrounded). Thus, we\n * use the `offset*` properties. See the linked GitHub issue.\n *\n * @see https://bit.ly/3m0k3he - GitHub issue\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n  el: HTMLElement\n): Observable<ElementSize> {\n  return observer$\n    .pipe(\n      tap(observer => observer.observe(el)),\n      switchMap(observer => entry$\n        .pipe(\n          filter(({ target }) => target === el),\n          finalize(() => observer.unobserve(el)),\n          map(() => getElementSize(el))\n        )\n      ),\n      startWith(getElementSize(el))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  map,\n  startWith\n} from \"rxjs/operators\"\n\nimport {\n  getElementContentSize,\n  getElementSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(el: HTMLElement): ElementOffset {\n  return {\n    x: el.scrollLeft,\n    y: el.scrollTop\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n  el: HTMLElement\n): Observable<ElementOffset> {\n  return merge(\n    fromEvent(el, \"scroll\"),\n    fromEvent(window, \"resize\")\n  )\n    .pipe(\n      map(() => getElementOffset(el)),\n      startWith(getElementOffset(el))\n    )\n}\n\n/**\n * Watch element threshold\n *\n * This function returns an observable which emits whether the bottom scroll\n * offset of an elements is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element threshold observable\n */\nexport function watchElementThreshold(\n  el: HTMLElement, threshold = 16\n): Observable<boolean> {\n  return watchElementOffset(el)\n    .pipe(\n      map(({ y }) => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return y >= (\n          content.height - visible.height - threshold\n        )\n      }),\n      distinctUntilChanged()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element text selection\n *\n * @param el - Element\n */\nexport function setElementSelection(\n  el: HTMLElement\n): void {\n  if (el instanceof HTMLInputElement)\n    el.select()\n  else\n    throw new Error(\"Not implemented\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getElementOrThrow } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n  | \"drawer\"                           /* Toggle for drawer */\n  | \"search\"                           /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record<Toggle, HTMLInputElement> = {\n  drawer: getElementOrThrow(\"[data-md-toggle=drawer]\"),\n  search: getElementOrThrow(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n  return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n  if (toggles[name].checked !== value)\n    toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable<boolean> {\n  const el = toggles[name]\n  return fromEvent(el, \"change\")\n    .pipe(\n      map(() => el.checked),\n      startWith(el.checked)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { filter, map, share } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n  | \"global\"                           /* Global */\n  | \"search\"                           /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n  mode: KeyboardMode                   /* Keyboard mode */\n  type: string                         /* Key type */\n  claim(): void                        /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(el: HTMLElement): boolean {\n  switch (el.tagName) {\n\n    /* Form elements */\n    case \"INPUT\":\n    case \"SELECT\":\n    case \"TEXTAREA\":\n      return true\n\n    /* Everything else */\n    default:\n      return el.isContentEditable\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable<Keyboard> {\n  return fromEvent<KeyboardEvent>(window, \"keydown\")\n    .pipe(\n      filter(ev => !(ev.metaKey || ev.ctrlKey)),\n      map(ev => ({\n        mode: getToggle(\"search\") ? \"search\" : \"global\",\n        type: ev.key,\n        claim() {\n          ev.preventDefault()\n          ev.stopPropagation()\n        }\n      } as Keyboard)),\n      filter(({ mode }) => {\n        if (mode === \"global\") {\n          const active = getActiveElement()\n          if (typeof active !== \"undefined\")\n            return !isSusceptibleToKeyboard(active)\n        }\n        return true\n      }),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n  return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n  location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject<URL> {\n  return new Subject<URL>()\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport { filter, map, share, startWith, switchMap } from \"rxjs/operators\"\n\nimport { createElement, getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n  return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n  const el = createElement(\"a\")\n  el.href = hash\n  el.addEventListener(\"click\", ev => ev.stopPropagation())\n  el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable<string> {\n  return fromEvent<HashChangeEvent>(window, \"hashchange\")\n    .pipe(\n      map(getLocationHash),\n      startWith(getLocationHash()),\n      filter(hash => hash.length > 0),\n      share()\n    )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable<HTMLElement> {\n  return watchLocationHash()\n    .pipe(\n      switchMap(id => of(getElement(`[id=\"${id}\"]`)!))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  filter,\n  map,\n  mapTo,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable<boolean> {\n  const media = matchMedia(query)\n  return fromEvent<MediaQueryListEvent>(media, \"change\")\n    .pipe(\n      map(ev => ev.matches),\n      startWith(media.matches)\n    )\n}\n\n/**\n * Watch print mode, cross-browser\n *\n * @returns Print mode observable\n */\nexport function watchPrint(): Observable<void> {\n  return merge(\n    watchMedia(\"print\").pipe(filter(Boolean)),  /* Webkit */\n    fromEvent(window, \"beforeprint\")            /* IE, FF */\n  )\n    .pipe(\n      mapTo(undefined)\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at<T>(\n  query$: Observable<boolean>, factory: () => Observable<T>\n): Observable<T> {\n  return query$\n    .pipe(\n      switchMap(active => active ? factory() : NEVER)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, from } from \"rxjs\"\nimport {\n  filter,\n  map,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n  url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable<Response> {\n  return from(fetch(`${url}`, options))\n    .pipe(\n      filter(res => res.status === 200),\n    )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON<T>(\n  url: URL | string, options?: RequestInit\n): Observable<T> {\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.json()),\n      shareReplay(1)\n    )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n  url: URL | string, options?: RequestInit\n): Observable<Document> {\n  const dom = new DOMParser()\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/xml\")),\n      shareReplay(1)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n  return {\n    x: Math.max(0, pageXOffset),\n    y: Math.max(0, pageYOffset)\n  }\n}\n\n/**\n * Set viewport offset\n *\n * @param offset - Viewport offset\n */\nexport function setViewportOffset(\n  { x, y }: Partial<ViewportOffset>\n): void {\n  window.scrollTo(x || 0, y || 0)\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable<ViewportOffset> {\n  return merge(\n    fromEvent(window, \"scroll\", { passive: true }),\n    fromEvent(window, \"resize\", { passive: true })\n  )\n    .pipe(\n      map(getViewportOffset),\n      startWith(getViewportOffset())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n  width: number                        /* Viewport width */\n  height: number                       /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n  return {\n    width:  innerWidth,\n    height: innerHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable<ViewportSize> {\n  return fromEvent(window, \"resize\", { passive: true })\n    .pipe(\n      map(getViewportSize),\n      startWith(getViewportSize())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, combineLatest } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  map,\n  shareReplay\n} from \"rxjs/operators\"\n\nimport { Header } from \"~/components\"\n\nimport {\n  ViewportOffset,\n  watchViewportOffset\n} from \"../offset\"\nimport {\n  ViewportSize,\n  watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n  offset: ViewportOffset               /* Viewport offset */\n  size: ViewportSize                   /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch at options\n */\ninterface WatchAtOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable<Viewport> {\n  return combineLatest([\n    watchViewportOffset(),\n    watchViewportSize()\n  ])\n    .pipe(\n      map(([offset, size]) => ({ offset, size })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n  el: HTMLElement, { viewport$, header$ }: WatchAtOptions\n): Observable<Viewport> {\n  const size$ = viewport$\n    .pipe(\n      distinctUntilKeyChanged(\"size\")\n    )\n\n  /* Compute element offset */\n  const offset$ = combineLatest([size$, header$])\n    .pipe(\n      map((): ViewportOffset => ({\n        x: el.offsetLeft,\n        y: el.offsetTop\n      }))\n    )\n\n  /* Compute relative viewport, return hot observable */\n  return combineLatest([header$, viewport$, offset$])\n    .pipe(\n      map(([{ height }, { offset, size }, { x, y }]) => ({\n        offset: {\n          x: offset.x - x,\n          y: offset.y - y + height\n        },\n        size\n      }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, fromEvent } from \"rxjs\"\nimport {\n  map,\n  share,\n  switchMapTo,\n  tap,\n  throttle\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n  type: unknown                        /* Message type */\n  data?: unknown                       /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n  T extends WorkerMessage\n> {\n  tx$: Subject<T>                      /* Message transmission subject */\n  rx$: Observable<T>                   /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions<T extends WorkerMessage> {\n  tx$: Observable<T>                   /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker<T extends WorkerMessage>(\n  worker: Worker, { tx$ }: WatchOptions<T>\n): Observable<T> {\n\n  /* Intercept messages from worker-like objects */\n  const rx$ = fromEvent<MessageEvent>(worker, \"message\")\n    .pipe(\n      map(({ data }) => data as T)\n    )\n\n  /* Send and receive messages, return hot observable */\n  return tx$\n    .pipe(\n      throttle(() => rx$, { leading: true, trailing: true }),\n      tap(message => worker.postMessage(message)),\n      switchMapTo(rx$),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n  | \"header.autohide\"                  /* Hide header */\n  | \"navigation.expand\"                /* Automatic expansion */\n  | \"navigation.instant\"               /* Instant loading */\n  | \"navigation.sections\"              /* Sections navigation */\n  | \"navigation.tabs\"                  /* Tabs navigation */\n  | \"navigation.top\"                   /* Back-to-top button */\n  | \"toc.integrate\"                    /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n  | \"clipboard.copy\"                   /* Copy to clipboard */\n  | \"clipboard.copied\"                 /* Copied to clipboard */\n  | \"search.config.lang\"               /* Search language */\n  | \"search.config.pipeline\"           /* Search pipeline */\n  | \"search.config.separator\"          /* Search separator */\n  | \"search.placeholder\"               /* Search */\n  | \"search.result.placeholder\"        /* Type to start searching */\n  | \"search.result.none\"               /* No matching documents */\n  | \"search.result.one\"                /* 1 matching document */\n  | \"search.result.other\"              /* # matching documents */\n  | \"search.result.more.one\"           /* 1 more on this page */\n  | \"search.result.more.other\"         /* # more on this page */\n  | \"search.result.term.missing\"       /* Missing */\n\n/**\n * Translations\n */\nexport type Translations = Record<Translation, string>\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n  provider: \"mike\"                     /* Version provider */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n  base: string                         /* Base URL */\n  features: Flag[]                     /* Feature flags */\n  translations: Translations           /* Translations */\n  search: string                       /* Search worker URL */\n  version?: Versioning                 /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElementOrThrow(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = new URL(config.base, getLocation())\n  .toString()\n  .replace(/\\/$/, \"\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n  return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n  return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n  key: Translation, value?: string | number\n): string {\n  return typeof value !== \"undefined\"\n    ? config.translations[key].replace(\"#\", value.toString())\n    : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component\n */\nexport type ComponentType =\n  | \"announce\"                         /* Announcement bar */\n  | \"container\"                        /* Container */\n  | \"content\"                          /* Content */\n  | \"dialog\"                           /* Dialog */\n  | \"header\"                           /* Header */\n  | \"header-title\"                     /* Header title */\n  | \"header-topic\"                     /* Header topic */\n  | \"main\"                             /* Main area */\n  | \"palette\"                          /* Color palette */\n  | \"search\"                           /* Search */\n  | \"search-query\"                     /* Search input */\n  | \"search-result\"                    /* Search results */\n  | \"sidebar\"                          /* Sidebar */\n  | \"skip\"                             /* Skip link */\n  | \"source\"                           /* Repository information */\n  | \"tabs\"                             /* Navigation tabs */\n  | \"toc\"                              /* Table of contents */\n  | \"top\"                              /* Back-to-top button */\n\n/**\n * A component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n  T extends {} = {},\n  U extends HTMLElement = HTMLElement\n> =\n  T & {\n    ref: U                             /* Component reference */\n  }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n  \"announce\": HTMLElement              /* Announcement bar */\n  \"container\": HTMLElement             /* Container */\n  \"content\": HTMLElement               /* Content */\n  \"dialog\": HTMLElement                /* Dialog */\n  \"header\": HTMLElement                /* Header */\n  \"header-title\": HTMLElement          /* Header title */\n  \"header-topic\": HTMLElement          /* Header topic */\n  \"main\": HTMLElement                  /* Main area */\n  \"palette\": HTMLElement               /* Color palette */\n  \"search\": HTMLElement                /* Search */\n  \"search-query\": HTMLInputElement     /* Search input */\n  \"search-result\": HTMLElement         /* Search results */\n  \"sidebar\": HTMLElement               /* Sidebar */\n  \"skip\": HTMLAnchorElement            /* Skip link */\n  \"source\": HTMLAnchorElement          /* Repository information */\n  \"tabs\": HTMLElement                  /* Navigation tabs */\n  \"toc\": HTMLElement                   /* Table of contents */\n  \"top\": HTMLAnchorElement             /* Back-to-top button */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n  return getElementOrThrow(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n  return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  switchMap,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetFocusable, setFocusable } from \"~/actions\"\nimport {\n  Viewport,\n  getElementContentSize,\n  getElementSize,\n  getElements,\n  watchMedia\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n  scroll: boolean                      /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global index for Clipboard.js integration\n */\nlet index = 0\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n  el: HTMLElement, { viewport$ }: WatchOptions\n): Observable<CodeBlock> {\n  const container$ = of(el)\n    .pipe(\n      switchMap(child => {\n        const container = child.closest(\"[data-tabs]\")\n        if (container instanceof HTMLElement) {\n          return merge(\n            ...getElements(\"input\", container)\n              .map(input => fromEvent(input, \"change\"))\n          )\n        }\n        return NEVER\n      })\n    )\n\n  /* Check overflow on resize and tab change */\n  return merge(\n    viewport$.pipe(distinctUntilKeyChanged(\"size\")),\n    container$\n  )\n    .pipe(\n      map(() => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return {\n          scroll: content.width > visible.width\n        }\n      }),\n      distinctUntilKeyChanged(\"scroll\")\n    )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block component observable\n */\nexport function mountCodeBlock(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<CodeBlock>> {\n  const internal$ = new Subject<CodeBlock>()\n  internal$\n    .pipe(\n      withLatestFrom(watchMedia(\"(hover)\"))\n    )\n      .subscribe(([{ scroll }, hover]) => {\n        if (scroll && hover)\n          setFocusable(el)\n        else\n          resetFocusable(el)\n      })\n\n  /* Render button for Clipboard.js integration */\n  if (ClipboardJS.isSupported()) {\n    const parent = el.closest(\"pre\")!\n    parent.id = `__code_${index++}`\n    parent.insertBefore(\n      renderClipboardButton(parent.id),\n      el\n    )\n  }\n\n  /* Create and return component */\n  return watchCodeBlock(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set focusable property\n *\n * @param el - Element\n * @param value - Tabindex value\n */\nexport function setFocusable(\n  el: HTMLElement, value = 0\n): void {\n  el.setAttribute(\"tabindex\", value.toString())\n}\n\n/**\n * Reset focusable property\n *\n * @param el - Element\n */\nexport function resetFocusable(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"tabindex\")\n}\n\n/**\n * Set scroll lock\n *\n * @param el - Scrollable element\n * @param value - Vertical offset\n */\nexport function setScrollLock(\n  el: HTMLElement, value: number\n): void {\n  el.setAttribute(\"data-md-state\", \"lock\")\n  el.style.top = `-${value}px`\n}\n\n/**\n * Reset scroll lock\n *\n * @param el - Scrollable element\n */\nexport function resetScrollLock(\n  el: HTMLElement\n): void {\n  const value = -1 * parseInt(el.style.top, 10)\n  el.removeAttribute(\"data-md-state\")\n  el.style.top = \"\"\n  if (value)\n    window.scrollTo(0, value)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set anchor state\n *\n * @param el - Anchor element\n * @param state - Anchor state\n */\nexport function setAnchorState(\n  el: HTMLElement, state: \"blur\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset anchor state\n *\n * @param el - Anchor element\n */\nexport function resetAnchorState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set anchor active\n *\n * @param el - Anchor element\n * @param value - Whether the anchor is active\n */\nexport function setAnchorActive(\n  el: HTMLElement, value: boolean\n): void {\n  el.classList.toggle(\"md-nav__link--active\", value)\n}\n\n/**\n * Reset anchor active\n *\n * @param el - Anchor element\n */\nexport function resetAnchorActive(\n  el: HTMLElement\n): void {\n  el.classList.remove(\"md-nav__link--active\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set dialog message\n *\n * @param el - Dialog element\n * @param value - Dialog message\n */\nexport function setDialogMessage(\n  el: HTMLElement, value: string\n): void {\n  el.firstElementChild!.innerHTML = value\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set dialog state\n *\n * @param el - Dialog element\n * @param state - Dialog state\n */\nexport function setDialogState(\n  el: HTMLElement, state: \"open\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset dialog state\n *\n * @param el - Dialog element\n */\nexport function resetDialogState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header state\n *\n * @param el - Header element\n * @param state - Header state\n */\nexport function setHeaderState(\n  el: HTMLElement, state: \"shadow\" | \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header state\n *\n * @param el - Header element\n */\nexport function resetHeaderState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header title state\n *\n * @param el - Header title element\n * @param state - Header title state\n */\nexport function setHeaderTitleState(\n  el: HTMLElement, state: \"active\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header title state\n *\n * @param el - Header title element\n */\nexport function resetHeaderTitleState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set search query placeholder\n *\n * @param el - Search query element\n * @param value - Placeholder\n */\nexport function setSearchQueryPlaceholder(\n  el: HTMLInputElement, value: string\n): void {\n  el.placeholder = value\n}\n\n/**\n * Reset search query placeholder\n *\n * @param el - Search query element\n */\nexport function resetSearchQueryPlaceholder(\n  el: HTMLInputElement\n): void {\n  el.placeholder = translation(\"search.placeholder\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n  & JSXInternal.HTMLAttributes\n  & JSXInternal.SVGAttributes\n  & Record<string, any>\n\n/**\n * Child element\n */\ntype Child =\n  | HTMLElement\n  | Text\n  | string\n  | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n  /* Handle primitive types (including raw HTML) */\n  if (typeof child === \"string\" || typeof child === \"number\") {\n    el.innerHTML += child.toString()\n\n  /* Handle nodes */\n  } else if (child instanceof Node) {\n    el.appendChild(child)\n\n  /* Handle nested children */\n  } else if (Array.isArray(child)) {\n    for (const node of child)\n      appendChild(el, node)\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n  tag: string, attributes: Attributes | null, ...children: Child[]\n): HTMLElement {\n  const el = document.createElement(tag)\n\n  /* Set attributes, if any */\n  if (attributes)\n    for (const attr of Object.keys(attributes))\n      if (typeof attributes[attr] !== \"boolean\")\n        el.setAttribute(attr, attributes[attr])\n      else if (attributes[attr])\n        el.setAttribute(attr, \"\")\n\n  /* Append child nodes */\n  for (const child of children)\n    appendChild(el, child)\n\n  /* Return element */\n  return el\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n  namespace JSX {\n    type Element = HTMLElement\n    type IntrinsicElements = JSXInternal.IntrinsicElements\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n  let i = n\n  if (value.length > i) {\n    while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n    return `${value.substring(0, i)}...`\n  }\n  return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n  if (value > 999) {\n    const digits = +((value - 950) % 1000 > 99)\n    return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n  } else {\n    return value.toString()\n  }\n}\n\n/**\n * Simple hash function\n *\n * @see https://bit.ly/2wsVjJ4 - Original source\n *\n * @param value - Value to be hashed\n *\n * @returns Hash as 32bit integer\n */\nexport function hash(value: string): number {\n  let h = 0\n  for (let i = 0, len = value.length; i < len; i++) {\n    h  = ((h << 5) - h) + value.charCodeAt(i)\n    h |= 0 // Convert to 32bit integer\n  }\n  return h\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set number of search results\n *\n * @param el - Search result metadata element\n * @param value - Number of results\n */\nexport function setSearchResultMeta(\n  el: HTMLElement, value: number\n): void {\n  switch (value) {\n\n    /* No results */\n    case 0:\n      el.textContent = translation(\"search.result.none\")\n      break\n\n    /* One result */\n    case 1:\n      el.textContent = translation(\"search.result.one\")\n      break\n\n    /* Multiple result */\n    default:\n      el.textContent = translation(\"search.result.other\", round(value))\n  }\n}\n\n/**\n * Reset number of search results\n *\n * @param el - Search result metadata element\n */\nexport function resetSearchResultMeta(\n  el: HTMLElement\n): void {\n  el.textContent = translation(\"search.result.placeholder\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Add an element to the search result list\n *\n * @param el - Search result list element\n * @param child - Search result element\n */\nexport function addToSearchResultList(\n  el: HTMLElement, child: Element\n): void {\n  el.appendChild(child)\n}\n\n/**\n * Reset search result list\n *\n * @param el - Search result list element\n */\nexport function resetSearchResultList(\n  el: HTMLElement\n): void {\n  el.innerHTML = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar offset\n *\n * @param el - Sidebar element\n * @param value - Sidebar offset\n */\nexport function setSidebarOffset(\n  el: HTMLElement, value: number\n): void {\n  el.style.top = `${value}px`\n}\n\n/**\n * Reset sidebar offset\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarOffset(\n  el: HTMLElement\n): void {\n  el.style.top = \"\"\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar height\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param value - Sidebar height\n */\nexport function setSidebarHeight(\n  el: HTMLElement, value: number\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`\n}\n\n/**\n * Reset sidebar height\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarHeight(\n  el: HTMLElement\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set repository facts\n *\n * @param el - Repository element\n * @param child - Repository facts element\n */\nexport function setSourceFacts(\n  el: HTMLElement, child: Element\n): void {\n  el.lastElementChild!.appendChild(child)\n}\n\n/**\n * Set repository state\n *\n * @param el - Repository element\n * @param state - Repository state\n */\nexport function setSourceState(\n  el: HTMLElement, state: \"done\"\n): void {\n  el.lastElementChild!.setAttribute(\"data-md-state\", state)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set tabs state\n *\n * @param el - Tabs element\n * @param state - Tabs state\n */\nexport function setTabsState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset tabs state\n *\n * @param el - Tabs element\n */\nexport function resetTabsState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set back-to-top state\n *\n * @param el - Back-to-top element\n * @param state - Back-to-top state\n */\nexport function setBackToTopState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset back-to-top state\n *\n * @param el - Back-to-top element\n */\nexport function resetBackToTopState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n  return (\n    <button\n      class=\"md-clipboard md-icon\"\n      title={translation(\"clipboard.copy\")}\n      data-clipboard-target={`#${id} > code`}\n    ></button>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport {\n  SearchDocument,\n  SearchMetadata,\n  SearchResult\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n  TEASER = 1,                          /* Render teaser */\n  PARENT = 2                           /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n  document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n  const parent = flag & Flag.PARENT\n  const teaser = flag & Flag.TEASER\n\n  /* Render missing query terms */\n  const missing = Object.keys(document.terms)\n    .filter(key => !document.terms[key])\n    .map(key => [<del>{key}</del>, \" \"])\n    .flat()\n    .slice(0, -1)\n\n  /* Render article or section, depending on flags */\n  const url = document.location\n  return (\n    <a href={url} class=\"md-search-result__link\" tabIndex={-1}>\n      <article\n        class={[\"md-search-result__article\", ...parent\n          ? [\"md-search-result__article--document\"]\n          : []\n        ].join(\" \")}\n        data-md-score={document.score.toFixed(2)}\n      >\n        {parent > 0 && <div class=\"md-search-result__icon md-icon\"></div>}\n        <h1 class=\"md-search-result__title\">{document.title}</h1>\n        {teaser > 0 && document.text.length > 0 &&\n          <p class=\"md-search-result__teaser\">\n            {truncate(document.text, 320)}\n          </p>\n        }\n        {teaser > 0 && missing.length > 0 &&\n          <p class=\"md-search-result__terms\">\n            {translation(\"search.result.term.missing\")}: {...missing}\n          </p>\n        }\n      </article>\n    </a>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResult(\n  result: SearchResult\n): HTMLElement {\n  const threshold = result[0].score\n  const docs = [...result]\n\n  /* Find and extract parent article */\n  const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n  const [article] = docs.splice(parent, 1)\n\n  /* Determine last index above threshold */\n  let index = docs.findIndex(doc => doc.score < threshold)\n  if (index === -1)\n    index = docs.length\n\n  /* Partition sections */\n  const best = docs.slice(0, index)\n  const more = docs.slice(index)\n\n  /* Render children */\n  const children = [\n    renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n    ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n    ...more.length ? [\n      <details class=\"md-search-result__more\">\n        <summary tabIndex={-1}>\n          {more.length > 0 && more.length === 1\n            ? translation(\"search.result.more.one\")\n            : translation(\"search.result.more.other\", more.length)\n          }\n        </summary>\n        {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n      </details>\n    ] : []\n  ]\n\n  /* Render search result */\n  return (\n    <li class=\"md-search-result__item\">\n      {children}\n    </li>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h, round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n  return (\n    <ul class=\"md-source__facts\">\n      {Object.entries(facts).map(([key, value]) => (\n        <li class={`md-source__fact md-source__fact--${key}`}>\n          {typeof value === \"number\" ? round(value) : value}\n        </li>\n      ))}\n    </ul>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n  return (\n    <div class=\"md-typeset__scrollwrap\">\n      <div class=\"md-typeset__table\">\n        {table}\n      </div>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n  version: string                      /* Version identifier */\n  title: string                        /* Version title */\n  aliases: string[]                    /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version\n *\n * @param version - Version\n *\n * @returns Element\n */\nfunction renderVersion(version: Version): HTMLElement {\n  const config = configuration()\n\n  /* Ensure trailing slash, see https://bit.ly/3rL5u3f */\n  const url = new URL(`${version.version}/`, config.base)\n  return (\n    <li class=\"md-version__item\">\n      <a href={url.toString()} class=\"md-version__link\">\n        {version.title}\n      </a>\n    </li>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n *\n * @returns Element\n */\nexport function renderVersionSelector(versions: Version[]): HTMLElement {\n  const config = configuration()\n\n  /* Determine active version */\n  const [, current] = config.base.match(/([^/]+)\\/?$/)!\n  const active =\n    versions.find(({ version, aliases }) => (\n      version === current || aliases.includes(current)\n    )) || versions[0]\n\n  /* Render version selector */\n  return (\n    <div class=\"md-version\">\n      <span class=\"md-version__current\">\n        {active.title}\n      </span>\n      <ul class=\"md-version__list\">\n        {versions.map(renderVersion)}\n      </ul>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject } from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  mapTo,\n  mergeWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n  el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable<Details> {\n  return target$\n    .pipe(\n      map(target => target.closest(\"details:not([open])\")!),\n      filter(details => el === details),\n      mergeWith(print$),\n      mapTo(el)\n    )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n  el: HTMLDetailsElement, options: MountOptions\n): Observable<Component<Details>> {\n  const internal$ = new Subject<Details>()\n  internal$.subscribe(() => {\n    el.setAttribute(\"open\", \"\")\n    el.scrollIntoView()\n  })\n\n  /* Create and return component */\n  return watchDetails(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      mapTo({ ref: el })\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { createElement, replaceElement } from \"~/browser\"\nimport { renderTable } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = createElement(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n  el: HTMLElement\n): Observable<Component<DataTable>> {\n  replaceElement(el, sentinel)\n  replaceElement(sentinel, renderTable(el))\n\n  /* Create and return component */\n  return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { Viewport, getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { CodeBlock, mountCodeBlock } from \"../code\"\nimport { Details, mountDetails } from \"../details\"\nimport { DataTable, mountDataTable } from \"../table\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n  | CodeBlock\n  | DataTable\n  | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n  el: HTMLElement, { target$, viewport$, print$ }: MountOptions\n): Observable<Component<Content>> {\n  return merge(\n\n    /* Code blocks */\n    ...getElements(\"pre > code\", el)\n      .map(child => mountCodeBlock(child, { viewport$ })),\n\n    /* Data tables */\n    ...getElements(\"table:not([class])\", el)\n      .map(child => mountDataTable(child)),\n\n    /* Details */\n    ...getElements(\"details\", el)\n      .map(child => mountDetails(child, { target$, print$ }))\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetDialogState,\n  setDialogMessage,\n  setDialogState\n} from \"~/actions\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n  message: string                      /* Dialog message */\n  open: boolean                        /* Dialog is visible */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n  _el: HTMLElement, { alert$ }: WatchOptions\n): Observable<Dialog> {\n  return alert$\n    .pipe(\n      switchMap(message => merge(\n        of(true),\n        of(false).pipe(delay(2000))\n      )\n        .pipe(\n          map(open => ({ message, open }))\n        )\n      )\n    )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right cornerwhen a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Dialog>> {\n  const internal$ = new Subject<Dialog>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ message, open }) => {\n        setDialogMessage(el, message)\n        if (open)\n          setDialogState(el, \"open\")\n        else\n          resetDialogState(el)\n      })\n\n  /* Create and return component */\n  return watchDialog(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  combineLatestWith,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  observeOn,\n  shareReplay,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { feature } from \"~/_\"\nimport { resetHeaderState, setHeaderState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n  height: number                       /* Header visible height */\n  sticky: boolean                      /* Header stickyness */\n  hidden: boolean                      /* User scrolled past threshold */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable<boolean> {\n  if (!feature(\"header.autohide\"))\n    return of(false)\n\n  /* Compute direction and turning point */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => [a < b, b] as const),\n      distinctUntilKeyChanged(0)\n    )\n\n  /* Compute whether header should be hidden */\n  const hidden$ = combineLatest([viewport$, direction$])\n    .pipe(\n      filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n      map(([, [direction]]) => direction),\n      distinctUntilChanged()\n    )\n\n  /* Compute threshold for hiding */\n  const search$ = watchToggle(\"search\")\n  return combineLatest([viewport$, search$])\n    .pipe(\n      map(([{ offset }, search]) => offset.y > 400 && !search),\n      distinctUntilChanged(),\n      switchMap(active => active ? hidden$ : of(false)),\n      startWith(false)\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n  el: HTMLElement, options: WatchOptions\n): Observable<Header> {\n  return defer(() => {\n    const styles = getComputedStyle(el)\n    return of(\n      styles.position === \"sticky\" ||\n      styles.position === \"-webkit-sticky\"\n    )\n  })\n    .pipe(\n      combineLatestWith(watchElementSize(el), isHidden(options)),\n      map(([sticky, { height }, hidden]) => ({\n        height: sticky ? height : 0,\n        sticky,\n        hidden\n      })),\n      distinctUntilChanged((a, b) => (\n        a.sticky === b.sticky &&\n        a.height === b.height &&\n        a.hidden === b.hidden\n      )),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n  el: HTMLElement, { header$, main$ }: MountOptions\n): Observable<Component<Header>> {\n  const internal$ = new Subject<Main>()\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"active\"),\n      combineLatestWith(header$),\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(([{ active }, { hidden }]) => {\n        if (active)\n          setHeaderState(el, hidden ? \"hidden\" : \"shadow\")\n        else\n          resetHeaderState(el)\n      })\n\n  /* Connect to long-living subject and return component */\n  main$.subscribe(main => internal$.next(main))\n  return header$\n    .pipe(\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  animationFrameScheduler\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetHeaderTitleState,\n  setHeaderTitleState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n  active: boolean                      /* User scrolled past first headline */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n  el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions\n): Observable<HeaderTitle> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        const { height } = getElementSize(el)\n        return {\n          active: y >= height\n        }\n      }),\n      distinctUntilKeyChanged(\"active\")\n    )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<HeaderTitle>> {\n  const internal$ = new Subject<HeaderTitle>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ active }) => {\n        if (active)\n          setHeaderTitleState(el, \"active\")\n        else\n          resetHeaderTitleState(el)\n      })\n\n  /* Obtain headline, if any */\n  const headline = getElement<HTMLHeadingElement>(\"article h1\")\n  if (typeof headline === \"undefined\")\n    return NEVER\n\n  /* Create and return component */\n  return watchHeaderTitle(headline, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  map,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { Viewport, watchElementSize } from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n  offset: number                       /* Main area top offset */\n  height: number                       /* Main area visible height */\n  active: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Main> {\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(({ height }) => height),\n      distinctUntilChanged()\n    )\n\n  /* Compute the main area's top and bottom borders */\n  const border$ = adjust$\n    .pipe(\n      switchMap(() => watchElementSize(el)\n        .pipe(\n          map(({ height }) => ({\n            top:    el.offsetTop,\n            bottom: el.offsetTop + height\n          })),\n          distinctUntilKeyChanged(\"bottom\")\n        )\n      )\n    )\n\n  /* Compute the main area's offset, visible height and if we scrolled past */\n  return combineLatest([adjust$, border$, viewport$])\n    .pipe(\n      map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n        height = Math.max(0, height\n          - Math.max(0, top    - y,  header)\n          - Math.max(0, height + y - bottom)\n        )\n        return {\n          offset: top - header,\n          height,\n          active: top - header <= y\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.offset === b.offset &&\n        a.height === b.height &&\n        a.active === b.active\n      ))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  fromEvent,\n  of\n} from \"rxjs\"\nimport {\n  finalize,\n  map,\n  mapTo,\n  mergeMap,\n  shareReplay,\n  startWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Palette colors\n */\nexport interface PaletteColor {\n  scheme?: string                      /* Color scheme */\n  primary?: string                     /* Primary color */\n  accent?: string                      /* Accent color */\n}\n\n/**\n * Palette\n */\nexport interface Palette {\n  index: number                        /* Palette index */\n  color: PaletteColor                  /* Palette colors */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch color palette\n *\n * @param inputs - Color palette element\n *\n * @returns Color palette observable\n */\nexport function watchPalette(\n  inputs: HTMLInputElement[]\n): Observable<Palette> {\n  const data = localStorage.getItem(__prefix(\"__palette\"))!\n  const current = JSON.parse(data) || {\n    index: inputs.findIndex(input => (\n      matchMedia(input.getAttribute(\"data-md-color-media\")!).matches\n    ))\n  }\n\n  /* Emit changes in color palette */\n  const palette$ = of(...inputs)\n    .pipe(\n      mergeMap(input => fromEvent(input, \"change\")\n        .pipe(\n          mapTo(input)\n        )\n      ),\n      startWith(inputs[Math.max(0, current.index)]),\n      map(input => ({\n        index: inputs.indexOf(input),\n        color: {\n          scheme:  input.getAttribute(\"data-md-color-scheme\"),\n          primary: input.getAttribute(\"data-md-color-primary\"),\n          accent:  input.getAttribute(\"data-md-color-accent\")\n        }\n      } as Palette)),\n      shareReplay(1)\n    )\n\n  /* Persist preference in local storage */\n  palette$.subscribe(palette => {\n    localStorage.setItem(__prefix(\"__palette\"), JSON.stringify(palette))\n  })\n\n  /* Return palette */\n  return palette$\n}\n\n/**\n * Mount color palette\n *\n * @param el - Color palette element\n *\n * @returns Color palette component observable\n */\nexport function mountPalette(\n  el: HTMLElement\n): Observable<Component<Palette>> {\n  const internal$ = new Subject<Palette>()\n\n  /* Set color palette */\n  internal$.subscribe(palette => {\n    for (const [key, value] of Object.entries(palette.color))\n      if (typeof value === \"string\")\n        document.body.setAttribute(`data-md-color-${key}`, value)\n\n    /* Toggle visibility */\n    for (let index = 0; index < inputs.length; index++) {\n      const label = inputs[index].nextElementSibling as HTMLElement\n      label.hidden = palette.index !== index\n    }\n  })\n\n  /* Create and return component */\n  const inputs = getElements<HTMLInputElement>(\"input\", el)\n  return watchPalette(inputs)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport { Observable, Subject } from \"rxjs\"\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n  { alert$ }: SetupOptions\n): void {\n  if (ClipboardJS.isSupported()) {\n    new Observable<ClipboardJS.Event>(subscriber => {\n      new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\")\n        .on(\"success\", ev => subscriber.next(ev))\n    })\n      .subscribe(() => alert$.next(translation(\"clipboard.copied\")))\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  EMPTY,\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  catchError,\n  concatMap,\n  debounceTime,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  sample,\n  share,\n  skip,\n  skipUntil,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Viewport,\n  ViewportOffset,\n  createElement,\n  getElement,\n  getElements,\n  replaceElement,\n  request,\n  requestXML,\n  setLocation,\n  setLocationHash,\n  setViewportOffset\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n  url: URL                             /* State URL */\n  offset?: ViewportOffset              /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  document$: Subject<Document>         /* Document subject */\n  location$: Subject<URL>              /* Location subject */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns Processed URLs\n */\nfunction preprocess(urls: string[]): string[] {\n  if (urls.length < 2)\n    return urls\n\n  /* Take the first two URLs and remove everything after the last slash */\n  const [root, next] = urls\n    .sort((a, b) => a.length - b.length)\n    .map(url => url.replace(/[^/]+$/, \"\"))\n\n  /* Compute common prefix */\n  let index = 0\n  if (root === next)\n    index = root.length\n  else\n    while (root.charCodeAt(index) === next.charCodeAt(index))\n      index++\n\n  /* Replace common prefix (i.e. base) with effective base */\n  const config = configuration()\n  return urls.map(url => (\n    url.replace(root.slice(0, index), `${config.base}/`)\n  ))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n  { document$, location$, viewport$ }: SetupOptions\n): void {\n  const config = configuration()\n  if (location.protocol === \"file:\")\n    return\n\n  /* Disable automatic scroll restoration */\n  if (\"scrollRestoration\" in history) {\n    history.scrollRestoration = \"manual\"\n\n    /* Hack: ensure that reloads restore viewport offset */\n    fromEvent(window, \"beforeunload\")\n      .subscribe(() => {\n        history.scrollRestoration = \"auto\"\n      })\n  }\n\n  /* Hack: ensure absolute favicon link to omit 404s when switching */\n  const favicon = getElement<HTMLLinkElement>(\"link[rel=icon]\")\n  if (typeof favicon !== \"undefined\")\n    favicon.href = favicon.href\n\n  /* Intercept internal navigation */\n  const push$ = requestXML(`${config.base}/sitemap.xml`)\n    .pipe(\n      map(sitemap => preprocess(getElements(\"loc\", sitemap)\n        .map(node => node.textContent!)\n      )),\n      switchMap(urls => fromEvent<MouseEvent>(document.body, \"click\")\n        .pipe(\n          filter(ev => !ev.metaKey && !ev.ctrlKey),\n          switchMap(ev => {\n\n            /* Handle HTML and SVG elements */\n            if (ev.target instanceof Element) {\n              const el = ev.target.closest(\"a\")\n              if (el && !el.target && urls.includes(el.href)) {\n                ev.preventDefault()\n                return of({\n                  url: new URL(el.href)\n                })\n              }\n            }\n            return NEVER\n          })\n        )\n      ),\n      share<HistoryState>()\n    )\n\n  /* Intercept history back and forward */\n  const pop$ = fromEvent<PopStateEvent>(window, \"popstate\")\n    .pipe(\n      filter(ev => ev.state !== null),\n      map(ev => ({\n        url: new URL(location.href),\n        offset: ev.state\n      })),\n      share<HistoryState>()\n    )\n\n  /* Emit location change */\n  merge(push$, pop$)\n    .pipe(\n      distinctUntilChanged((a, b) => a.url.href === b.url.href),\n      map(({ url }) => url)\n    )\n      .subscribe(location$)\n\n  /* Fetch document via `XMLHTTPRequest` */\n  const response$ = location$\n    .pipe(\n      distinctUntilKeyChanged(\"pathname\"),\n      switchMap(url => request(url.href)\n        .pipe(\n          catchError(() => {\n            setLocation(url)\n            return NEVER\n          })\n        )\n      ),\n      share()\n    )\n\n  /* Set new location via `history.pushState` */\n  push$\n    .pipe(\n      sample(response$)\n    )\n      .subscribe(({ url }) => {\n        history.pushState({}, \"\", `${url}`)\n      })\n\n  /* Parse and emit fetched document */\n  const dom = new DOMParser()\n  response$\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/html\"))\n    )\n      .subscribe(document$)\n\n  /* Emit history state change */\n  merge(push$, pop$)\n    .pipe(\n      sample(document$)\n    )\n      .subscribe(({ url, offset }) => {\n        if (url.hash && !offset)\n          setLocationHash(url.hash)\n        else\n          setViewportOffset(offset || { y: 0 })\n      })\n\n  /* Replace meta tags and components */\n  document$\n    .pipe(\n      skip(1)\n    )\n      .subscribe(replacement => {\n        for (const selector of [\n\n          /* Meta tags */\n          \"title\",\n          \"link[rel=canonical]\",\n          \"meta[name=author]\",\n          \"meta[name=description]\",\n\n          /* Components */\n          \"[data-md-component=announce]\",\n          \"[data-md-component=container]\",\n          \"[data-md-component=header-topic]\",\n          \"[data-md-component=logo], .md-logo\", // compat\n          \"[data-md-component=skip]\"\n        ]) {\n          const source = getElement(selector)\n          const target = getElement(selector, replacement)\n          if (\n            typeof source !== \"undefined\" &&\n            typeof target !== \"undefined\"\n          ) {\n            replaceElement(source, target)\n          }\n        }\n      })\n\n  /* Re-evaluate scripts */\n  document$\n    .pipe(\n      skip(1),\n      map(() => getComponentElement(\"container\")),\n      switchMap(el => of(...getElements(\"script\", el))),\n      concatMap(el => {\n        const script = createElement(\"script\")\n        if (el.src) {\n          for (const name of el.getAttributeNames())\n            script.setAttribute(name, el.getAttribute(name)!)\n          replaceElement(el, script)\n\n          /* Complete when script is loaded */\n          return new Observable(observer => {\n            script.onload = () => observer.complete()\n          })\n\n        /* Complete immediately */\n        } else {\n          script.textContent = el.textContent\n          replaceElement(el, script)\n          return EMPTY\n        }\n      })\n    )\n      .subscribe()\n\n  /* Debounce update of viewport offset */\n  viewport$\n    .pipe(\n      skipUntil(push$),\n      debounceTime(250),\n      distinctUntilKeyChanged(\"offset\")\n    )\n      .subscribe(({ offset }) => {\n        history.replaceState(offset, \"\")\n      })\n\n  /* Set viewport offset from history */\n  merge(push$, pop$)\n    .pipe(\n      bufferCount(2, 1),\n      filter(([a, b]) => a.url.pathname === b.url.pathname),\n      map(([, state]) => state)\n    )\n      .subscribe(({ offset }) => {\n        setViewportOffset(offset || { y: 0 })\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n *    that the resulting document must contain all terms, converting the query\n *    to an `AND` query (as opposed to the default `OR` behavior). While users\n *    may expect terms enclosed in quotation marks to map to span queries, i.e.\n *    for which order is important, Lunr.js doesn't support them, so the best\n *    we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n *    query or preceded by white space, or are not followed by a non-whitespace\n *    character or are at the end of the query string. Furthermore, filter\n *    unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n  return query\n    .split(/\"([^\"]+)\"/g)                            /* => 1 */\n      .map((terms, index) => index & 1\n        ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n        : terms\n      )\n      .join(\"\")\n    .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n    .trim()                                         /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ObservableInput, Subject, from } from \"rxjs\"\nimport { map, share } from \"rxjs/operators\"\n\nimport { configuration, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex, SearchIndexPipeline } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType,\n  SearchSetupMessage,\n  isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler<SearchMessage>\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex(\n  { config, docs, index }: SearchIndex\n): SearchIndex {\n\n  /* Override default language with value from translation */\n  if (config.lang.length === 1 && config.lang[0] === \"en\")\n    config.lang = [\n      translation(\"search.config.lang\")\n    ]\n\n  /* Override default separator with value from translation */\n  if (config.separator === \"[\\\\s\\\\-]+\")\n    config.separator = translation(\"search.config.separator\")\n\n  /* Set pipeline from translation */\n  const pipeline = translation(\"search.config.pipeline\")\n    .split(/\\s*,\\s*/)\n    .filter(Boolean) as SearchIndexPipeline\n\n  /* Return search index after defaulting */\n  return { config, docs, index, pipeline }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n  url: string, index: ObservableInput<SearchIndex>\n): SearchWorker {\n  const config = configuration()\n  const worker = new Worker(url)\n\n  /* Create communication channels and resolve relative links */\n  const tx$ = new Subject<SearchMessage>()\n  const rx$ = watchWorker(worker, { tx$ })\n    .pipe(\n      map(message => {\n        if (isSearchResultMessage(message)) {\n          for (const result of message.data)\n            for (const document of result)\n              document.location = `${config.base}/${document.location}`\n        }\n        return message\n      }),\n      share()\n    )\n\n  /* Set up search index */\n  from(index)\n    .pipe(\n      map<SearchIndex, SearchSetupMessage>(data => ({\n        type: SearchMessageType.SETUP,\n        data: setupSearchIndex(data)\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Return search worker */\n  return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { getElementOrThrow, requestJSON } from \"~/browser\"\nimport { Version, renderVersionSelector } from \"~/templates\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n */\nexport function setupVersionSelector(): void {\n  const config = configuration()\n  requestJSON<Version[]>(new URL(\"versions.json\", config.base))\n    .subscribe(versions => {\n      const topic = getElementOrThrow(\".md-header__topic\")\n      topic.appendChild(renderVersionSelector(versions))\n    })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  combineLatest,\n  fromEvent,\n  merge\n} from \"rxjs\"\nimport {\n  delay,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  takeLast,\n  takeUntil,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetSearchQueryPlaceholder,\n  setSearchQueryPlaceholder\n} from \"~/actions\"\nimport {\n  setElementFocus,\n  setToggle,\n  watchElementFocus\n} from \"~/browser\"\nimport {\n  SearchMessageType,\n  SearchQueryMessage,\n  SearchWorker,\n  defaultTransform\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n  value: string                        /* Query value */\n  focus: boolean                       /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n  el: HTMLInputElement\n): Observable<SearchQuery> {\n  const fn = __search?.transform || defaultTransform\n\n  /* Intercept focus and input events */\n  const focus$ = watchElementFocus(el)\n  const value$ = merge(\n    fromEvent(el, \"keyup\"),\n    fromEvent(el, \"focus\").pipe(delay(1))\n  )\n    .pipe(\n      map(() => fn(el.value)),\n      distinctUntilChanged()\n    )\n\n  /* Combine into single observable */\n  return combineLatest([value$, focus$])\n    .pipe(\n      map(([value, focus]) => ({ value, focus }))\n    )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n  el: HTMLInputElement, { tx$ }: SearchWorker\n): Observable<Component<SearchQuery, HTMLInputElement>> {\n  const internal$ = new Subject<SearchQuery>()\n\n  /* Handle value changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"value\"),\n      map(({ value }): SearchQueryMessage => ({\n        type: SearchMessageType.QUERY,\n        data: value\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Handle focus changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"focus\")\n    )\n      .subscribe(({ focus }) => {\n        if (focus) {\n          setToggle(\"search\", focus)\n          setSearchQueryPlaceholder(el, \"\")\n        } else {\n          resetSearchQueryPlaceholder(el)\n        }\n      })\n\n  /* Handle reset */\n  fromEvent(el.form!, \"reset\")\n    .pipe(\n      takeUntil(internal$.pipe(takeLast(1)))\n    )\n      .subscribe(() => setElementFocus(el))\n\n  /* Create and return component */\n  return watchSearchQuery(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  filter,\n  finalize,\n  map,\n  observeOn,\n  startWith,\n  switchMap,\n  tap,\n  withLatestFrom,\n  zipWith\n} from \"rxjs/operators\"\n\nimport {\n  addToSearchResultList,\n  resetSearchResultList,\n  resetSearchResultMeta,\n  setSearchResultMeta\n} from \"~/actions\"\nimport {\n  getElementOrThrow,\n  watchElementThreshold\n} from \"~/browser\"\nimport {\n  SearchResult as SearchResultData,\n  SearchWorker,\n  isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResult } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n  data: SearchResultData[]             /* Search result data */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  query$: Observable<SearchQuery>      /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n  el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable<Component<SearchResult>> {\n  const internal$ = new Subject<SearchResult>()\n  const boundary$ = watchElementThreshold(el.parentElement!)\n    .pipe(\n      filter(Boolean)\n    )\n\n  /* Update search result metadata */\n  const meta = getElementOrThrow(\":scope > :first-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(query$)\n    )\n      .subscribe(([{ data }, { value }]) => {\n        if (value)\n          setSearchResultMeta(meta, data.length)\n        else\n          resetSearchResultMeta(meta)\n      })\n\n  /* Update search result list */\n  const list = getElementOrThrow(\":scope > :last-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      tap(() => resetSearchResultList(list)),\n      switchMap(({ data }) => merge(\n        of(...data.slice(0, 10)),\n        of(...data.slice(10))\n          .pipe(\n            bufferCount(4),\n            zipWith(boundary$),\n            switchMap(([chunk]) => of(...chunk))\n          )\n      ))\n    )\n      .subscribe(result => {\n        addToSearchResultList(list, renderSearchResult(result))\n      })\n\n  /* Filter search result list */\n  const result$ = rx$\n    .pipe(\n      filter(isSearchResultMessage),\n      map(({ data }) => ({ data })),\n      startWith({ data: [] })\n    )\n\n  /* Create and return component */\n  return result$\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, ObservableInput, merge } from \"rxjs\"\nimport { filter, sample, take } from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Keyboard,\n  getActiveElement,\n  getElements,\n  setElementFocus,\n  setElementSelection,\n  setToggle\n} from \"~/browser\"\nimport {\n  SearchIndex,\n  isSearchQueryMessage,\n  isSearchReadyMessage,\n  setupSearchWorker\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\nimport { SearchQuery, mountSearchQuery } from \"../query\"\nimport { SearchResult, mountSearchResult } from \"../result\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n  | SearchQuery\n  | SearchResult\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  index$: ObservableInput<SearchIndex> /* Search index observable */\n  keyboard$: Observable<Keyboard>      /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n  el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable<Component<Search>> {\n  const config = configuration()\n  const worker = setupSearchWorker(config.search, index$)\n\n  /* Retrieve nested components */\n  const query  = getComponentElement(\"search-query\", el)\n  const result = getComponentElement(\"search-result\", el)\n\n  /* Re-emit query when search is ready */\n  const { tx$, rx$ } = worker\n  tx$\n    .pipe(\n      filter(isSearchQueryMessage),\n      sample(rx$.pipe(filter(isSearchReadyMessage))),\n      take(1)\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Set up search keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"search\")\n    )\n      .subscribe(key => {\n        const active = getActiveElement()\n        switch (key.type) {\n\n          /* Enter: prevent form submission */\n          case \"Enter\":\n            if (active === query)\n              key.claim()\n            break\n\n          /* Escape or Tab: close search */\n          case \"Escape\":\n          case \"Tab\":\n            setToggle(\"search\", false)\n            setElementFocus(query, false)\n            break\n\n          /* Vertical arrows: select previous or next search result */\n          case \"ArrowUp\":\n          case \"ArrowDown\":\n            if (typeof active === \"undefined\") {\n              setElementFocus(query)\n            } else {\n              const els = [query, ...getElements(\n                \":not(details) > [href], summary, details[open] [href]\",\n                result\n              )]\n              const i = Math.max(0, (\n                Math.max(0, els.indexOf(active)) + els.length + (\n                  key.type === \"ArrowUp\" ? -1 : +1\n                )\n              ) % els.length)\n              setElementFocus(els[i])\n            }\n\n            /* Prevent scrolling of page */\n            key.claim()\n            break\n\n          /* All other keys: hand to search query */\n          default:\n            if (query !== getActiveElement())\n              setElementFocus(query)\n        }\n      })\n\n  /* Set up global keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"global\"),\n    )\n      .subscribe(key => {\n        switch (key.type) {\n\n          /* Open search and select query */\n          case \"f\":\n          case \"s\":\n          case \"/\":\n            setElementFocus(query)\n            setElementSelection(query)\n            key.claim()\n            break\n        }\n      })\n\n  /* Create and return component */\n  const query$ = mountSearchQuery(query, worker)\n  return merge(\n    query$,\n    mountSearchResult(result, worker, { query$ })\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  finalize,\n  map,\n  observeOn,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport {\n  resetSidebarHeight,\n  resetSidebarOffset,\n  setSidebarHeight,\n  setSidebarOffset\n} from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n  height: number                       /* Sidebar height */\n  locked: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n  el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<Sidebar> {\n  const adjust =\n    el.parentElement!.offsetTop -\n    el.parentElement!.parentElement!.offsetTop\n\n  /* Compute the sidebar's available height and if it should be locked */\n  return combineLatest([main$, viewport$])\n    .pipe(\n      map(([{ offset, height }, { offset: { y } }]) => {\n        height = height\n          + Math.min(adjust, Math.max(0, y - offset))\n          - adjust\n        return {\n          height,\n          locked: y >= offset + adjust\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.height === b.height &&\n        a.locked === b.locked\n      ))\n    )\n}\n\n/**\n * Mount sidebar\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n  el: HTMLElement, { header$, ...options }: MountOptions\n): Observable<Component<Sidebar>> {\n  const internal$ = new Subject<Sidebar>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(header$)\n    )\n      .subscribe({\n\n        /* Update height and offset */\n        next([{ height }, { height: offset }]) {\n          setSidebarHeight(el, height)\n          setSidebarOffset(el, offset)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetSidebarOffset(el)\n          resetSidebarHeight(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchSidebar(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport { Observable, zip } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * GitHub release (partial)\n */\ninterface Release {\n  tag_name: string                     /* Tag name */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n  user: string, repo?: string\n): Observable<SourceFacts> {\n  if (typeof repo !== \"undefined\") {\n    const url = `https://api.github.com/repos/${user}/${repo}`\n    return zip(\n\n      /* Fetch version */\n      requestJSON<Release>(`${url}/releases/latest`)\n        .pipe(\n          map(release => ({\n            version: release.tag_name\n          })),\n          defaultIfEmpty({})\n        ),\n\n      /* Fetch stars and forks */\n      requestJSON<Repo>(url)\n        .pipe(\n          map(info => ({\n            stars: info.stargazers_count,\n            forks: info.forks_count\n          })),\n          defaultIfEmpty({})\n        )\n    )\n      .pipe(\n        map(([release, info]) => ({ ...release, ...info }))\n      )\n\n  /* User or organization */\n  } else {\n    const url = `https://api.github.com/repos/${user}`\n    return requestJSON<User>(url)\n      .pipe(\n        map(info => ({\n          repositories: info.public_repos\n        })),\n        defaultIfEmpty({})\n      )\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n  base: string, project: string\n): Observable<SourceFacts> {\n  const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n  return requestJSON<ProjectSchema>(url)\n    .pipe(\n      map(({ star_count, forks_count }) => ({\n        stars: star_count,\n        forks: forks_count\n      })),\n      defaultIfEmpty({})\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts for repositories\n */\nexport interface RepositoryFacts {\n  stars?: number                       /* Number of stars */\n  forks?: number                       /* Number of forks */\n  version?: string                     /* Latest version */\n}\n\n/**\n * Repository facts for organizations\n */\nexport interface OrganizationFacts {\n  repositories?: number                /* Number of repositories */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts =\n  | RepositoryFacts\n  | OrganizationFacts\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n  url: string\n): Observable<SourceFacts> {\n  const [type] = url.match(/(git(?:hub|lab))/i) || []\n  switch (type.toLowerCase()) {\n\n    /* GitHub repository */\n    case \"github\":\n      const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n      return fetchSourceFactsFromGitHub(user, repo)\n\n    /* GitLab repository */\n    case \"gitlab\":\n      const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n      return fetchSourceFactsFromGitLab(base, slug)\n\n    /* Everything else */\n    default:\n      return NEVER\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, Subject, defer, of } from \"rxjs\"\nimport {\n  catchError,\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  tap\n} from \"rxjs/operators\"\n\nimport { setSourceFacts, setSourceState } from \"~/actions\"\nimport { renderSourceFacts } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SourceFacts, fetchSourceFacts } from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n  facts: SourceFacts                   /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable<Source>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n  el: HTMLAnchorElement\n): Observable<Source> {\n  return fetch$ ||= defer(() => {\n    const data = sessionStorage.getItem(__prefix(\"__source\"))\n    if (data) {\n      return of<SourceFacts>(JSON.parse(data))\n    } else {\n      const value$ = fetchSourceFacts(el.href)\n      value$.subscribe(value => {\n        try {\n          sessionStorage.setItem(__prefix(\"__source\"), JSON.stringify(value))\n        } catch (err) {\n          /* Uncritical, just swallow */\n        }\n      })\n\n      /* Return value */\n      return value$\n    }\n  })\n    .pipe(\n      catchError(() => NEVER),\n      filter(facts => Object.keys(facts).length > 0),\n      map(facts => ({ facts })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n  el: HTMLAnchorElement\n): Observable<Component<Source>> {\n  const internal$ = new Subject<Source>()\n  internal$.subscribe(({ facts }) => {\n    setSourceFacts(el, renderSourceFacts(facts))\n    setSourceState(el, \"done\")\n  })\n\n  /* Create and return component */\n  return watchSource(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, animationFrameScheduler } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetTabsState, setTabsState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n  hidden: boolean                      /* User scrolled past tabs */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Tabs> {\n  return watchElementSize(document.body)\n    .pipe(\n      switchMap(() => watchViewportAt(el, { header$, viewport$ })),\n      map(({ offset: { y } }) => {\n        return {\n          hidden: y >= 10\n        }\n      }),\n      distinctUntilKeyChanged(\"hidden\")\n    )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Tabs>> {\n  const internal$ = new Subject<Tabs>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setTabsState(el, \"hidden\")\n          else\n            resetTabsState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetTabsState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchTabs(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  scan,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetAnchorActive,\n  resetAnchorState,\n  setAnchorActive,\n  setAnchorState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElements,\n  watchElementSize\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n  prev: HTMLAnchorElement[][]          /* Anchors (previous) */\n  next: HTMLAnchorElement[][]          /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param anchors - Anchor elements\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n  anchors: HTMLAnchorElement[], { viewport$, header$ }: WatchOptions\n): Observable<TableOfContents> {\n  const table = new Map<HTMLAnchorElement, HTMLElement>()\n  for (const anchor of anchors) {\n    const id = decodeURIComponent(anchor.hash.substring(1))\n    const target = getElement(`[id=\"${id}\"]`)\n    if (typeof target !== \"undefined\")\n      table.set(anchor, target)\n  }\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(header => 24 + header.height)\n    )\n\n  /* Compute partition of previous and next anchors */\n  const partition$ = watchElementSize(document.body)\n    .pipe(\n      distinctUntilKeyChanged(\"height\"),\n\n      /* Build index to map anchor paths to vertical offsets */\n      map(() => {\n        let path: HTMLAnchorElement[] = []\n        return [...table].reduce((index, [anchor, target]) => {\n          while (path.length) {\n            const last = table.get(path[path.length - 1])!\n            if (last.tagName >= target.tagName) {\n              path.pop()\n            } else {\n              break\n            }\n          }\n\n          /* If the current anchor is hidden, continue with its parent */\n          let offset = target.offsetTop\n          while (!offset && target.parentElement) {\n            target = target.parentElement\n            offset = target.offsetTop\n          }\n\n          /* Map reversed anchor path to vertical offset */\n          return index.set(\n            [...path = [...path, anchor]].reverse(),\n            offset\n          )\n        }, new Map<HTMLAnchorElement[], number>())\n      }),\n\n      /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */\n      map(index => new Map([...index].sort(([, a], [, b]) => a - b))),\n\n      /* Re-compute partition when viewport offset changes */\n      switchMap(index => combineLatest([adjust$, viewport$])\n        .pipe(\n          scan(([prev, next], [adjust, { offset: { y } }]) => {\n\n            /* Look forward */\n            while (next.length) {\n              const [, offset] = next[0]\n              if (offset - adjust < y) {\n                prev = [...prev, next.shift()!]\n              } else {\n                break\n              }\n            }\n\n            /* Look backward */\n            while (prev.length) {\n              const [, offset] = prev[prev.length - 1]\n              if (offset - adjust >= y) {\n                next = [prev.pop()!, ...next]\n              } else {\n                break\n              }\n            }\n\n            /* Return partition */\n            return [prev, next]\n          }, [[], [...index]]),\n          distinctUntilChanged((a, b) => (\n            a[0] === b[0] &&\n            a[1] === b[1]\n          ))\n        )\n      )\n    )\n\n  /* Compute and return anchor list migrations */\n  return partition$\n    .pipe(\n      map(([prev, next]) => ({\n        prev: prev.map(([path]) => path),\n        next: next.map(([path]) => path)\n      })),\n\n      /* Extract anchor list migrations */\n      startWith({ prev: [], next: [] }),\n      bufferCount(2, 1),\n      map(([a, b]) => {\n\n        /* Moving down */\n        if (a.prev.length < b.prev.length) {\n          return {\n            prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n            next: []\n          }\n\n        /* Moving up */\n        } else {\n          return {\n            prev: b.prev.slice(-1),\n            next: b.next.slice(0, b.next.length - a.next.length)\n          }\n        }\n      })\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Anchor list element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<TableOfContents>> {\n  const internal$ = new Subject<TableOfContents>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n    )\n      .subscribe(({ prev, next }) => {\n\n        /* Look forward */\n        for (const [anchor] of next) {\n          resetAnchorActive(anchor)\n          resetAnchorState(anchor)\n        }\n\n        /* Look backward */\n        for (const [index, [anchor]] of prev.entries()) {\n          setAnchorActive(anchor, index === prev.length - 1)\n          setAnchorState(anchor, \"blur\")\n        }\n      })\n\n  /* Create and return component */\n  const anchors = getElements<HTMLAnchorElement>(\"[href^=\\\\#]\", el)\n  return watchTableOfContents(anchors, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetBackToTopState, setBackToTopState } from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Back-to-top button\n */\nexport interface BackToTop {\n  hidden: boolean                      /* User scrolled up */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch back-to-top\n *\n * @param _el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top observable\n */\nexport function watchBackToTop(\n  _el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<BackToTop> {\n\n  /* Compute direction */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => a > b),\n      distinctUntilChanged()\n    )\n\n  /* Compute whether button should be hidden */\n  const hidden$ = main$\n    .pipe(\n      distinctUntilKeyChanged(\"active\")\n    )\n\n  /* Compute threshold for hiding */\n  return combineLatest([hidden$, direction$])\n    .pipe(\n      map(([{ active }, direction]) => ({\n        hidden: !(active && direction)\n      })),\n      distinctUntilChanged((a, b) => (\n        a.hidden === b.hidden\n      ))\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount back-to-top\n *\n * @param el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top component observable\n */\nexport function mountBackToTop(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<BackToTop>> {\n  const internal$ = new Subject<BackToTop>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setBackToTopState(el, \"hidden\")\n          else\n            resetBackToTopState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetBackToTopState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchBackToTop(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  mapTo,\n  mergeMap,\n  switchMap,\n  takeWhile,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n  { document$, tablet$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements<HTMLInputElement>(\n        \"[data-md-state=indeterminate]\"\n      ))),\n      tap(el => {\n        el.indeterminate = true\n        el.checked = false\n      }),\n      mergeMap(el => fromEvent(el, \"change\")\n        .pipe(\n          takeWhile(() => el.hasAttribute(\"data-md-state\")),\n          mapTo(el)\n        )\n      ),\n      withLatestFrom(tablet$)\n    )\n      .subscribe(([el, tablet]) => {\n        el.removeAttribute(\"data-md-state\")\n        if (tablet)\n          el.checked = false\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  filter,\n  mapTo,\n  mergeMap,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n  return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n  { document$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements(\"[data-md-scrollfix]\"))),\n      tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n      filter(isAppleDevice),\n      mergeMap(el => fromEvent(el, \"touchstart\")\n        .pipe(\n          mapTo(el)\n        )\n      )\n    )\n      .subscribe(el => {\n        const top = el.scrollTop\n\n        /* We're at the top of the container */\n        if (top === 0) {\n          el.scrollTop = 1\n\n        /* We're at the bottom of the container */\n        } else if (top + el.offsetHeight === el.scrollHeight) {\n          el.scrollTop = top - 1\n        }\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  animationFrameScheduler,\n  combineLatest,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  map,\n  observeOn,\n  switchMap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetScrollLock, setScrollLock } from \"~/actions\"\nimport { Viewport, watchToggle } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n  { viewport$, tablet$ }: PatchOptions\n): void {\n  combineLatest([watchToggle(\"search\"), tablet$])\n    .pipe(\n      map(([active, tablet]) => active && !tablet),\n      switchMap(active => of(active)\n        .pipe(\n          delay(active ? 400 : 100),\n          observeOn(animationFrameScheduler)\n        )\n      ),\n      withLatestFrom(viewport$)\n    )\n      .subscribe(([active, { offset: { y }}]) => {\n        if (active)\n          setScrollLock(document.body, y)\n        else\n          resetScrollLock(document.body)\n      })\n}\n"],
+  "mappings": "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",
+  "names": []
+}
diff --git a/5.4/assets/javascripts/workers/search.fb4a9340.min.js b/5.4/assets/javascripts/workers/search.fb4a9340.min.js
deleted file mode 100644 (file)
index bf8dbff..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-(()=>{var le=Object.create,U=Object.defineProperty,he=Object.getPrototypeOf,de=Object.prototype.hasOwnProperty,fe=Object.getOwnPropertyNames,pe=Object.getOwnPropertyDescriptor;var ge=t=>U(t,"__esModule",{value:!0});var q=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var ye=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of fe(e))!de.call(t,n)&&n!=="default"&&U(t,n,{get:()=>e[n],enumerable:!(r=pe(e,n))||r.enumerable});return t},Y=t=>t&&t.__esModule?t:ye(ge(U(t!=null?le(he(t)):{},"default",{value:t,enumerable:!0})),t);var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var X=q((G,J)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i<n.length;i++){var s=n[i],o=e[s];if(Array.isArray(o)){r[s]=o.slice();continue}if(typeof o=="string"||typeof o=="number"||typeof o=="boolean"){r[s]=o;continue}throw new TypeError("clone is not deep and does not support nested objects")}return r},t.FieldRef=function(e,r,n){this.docRef=e,this.fieldName=r,this._stringValue=n},t.FieldRef.joiner="/",t.FieldRef.fromString=function(e){var r=e.indexOf(t.FieldRef.joiner);if(r===-1)throw"malformed field ref string";var n=e.slice(0,r),i=e.slice(r+1);return new t.FieldRef(i,n,e)},t.FieldRef.prototype.toString=function(){return this._stringValue==null&&(this._stringValue=this.fieldName+t.FieldRef.joiner+this.docRef),this._stringValue};t.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var r=0;r<this.length;r++)this.elements[e[r]]=!0}else this.length=0},t.Set.complete={intersect:function(e){return e},union:function(){return this},contains:function(){return!0}},t.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},t.Set.prototype.contains=function(e){return!!this.elements[e]},t.Set.prototype.intersect=function(e){var r,n,i,s=[];if(e===t.Set.complete)return this;if(e===t.Set.empty)return e;this.length<e.length?(r=this,n=e):(r=e,n=this),i=Object.keys(r.elements);for(var o=0;o<i.length;o++){var a=i[o];a in n.elements&&s.push(a)}return new t.Set(s)},t.Set.prototype.union=function(e){return e===t.Set.complete?t.Set.complete:e===t.Set.empty?this:new t.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},t.idf=function(e,r){var n=0;for(var i in e)i!="_index"&&(n+=Object.keys(e[i]).length);var s=(r-n+.5)/(n+.5);return Math.log(1+Math.abs(s))},t.Token=function(e,r){this.str=e||"",this.metadata=r||{}},t.Token.prototype.toString=function(){return this.str},t.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},t.Token.prototype.clone=function(e){return e=e||function(r){return r},new t.Token(e(this.str,this.metadata),this.metadata)};t.tokenizer=function(e,r){if(e==null||e==null)return[];if(Array.isArray(e))return e.map(function(y){return new t.Token(t.utils.asString(y).toLowerCase(),t.utils.clone(r))});for(var n=e.toString().toLowerCase(),i=n.length,s=[],o=0,a=0;o<=i;o++){var u=n.charAt(o),c=o-a;if(u.match(t.tokenizer.separator)||o==i){if(c>0){var d=t.utils.clone(r)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index.
-`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n<r;n++){for(var i=this._stack[n],s=[],o=0;o<e.length;o++){var a=i(e[o],o,e);if(!(a==null||a===""))if(Array.isArray(a))for(var u=0;u<a.length;u++)s.push(a[u]);else s.push(a)}e=s}return e},t.Pipeline.prototype.runString=function(e,r){var n=new t.Token(e,r);return this.run([n]).map(function(i){return i.toString()})},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})};t.Vector=function(e){this._magnitude=0,this.elements=e||[]},t.Vector.prototype.positionForIndex=function(e){if(this.elements.length==0)return 0;for(var r=0,n=this.elements.length/2,i=n-r,s=Math.floor(i/2),o=this.elements[s*2];i>1&&(o<e&&(r=s),o>e&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(o<e)return(s+1)*2},t.Vector.prototype.insert=function(e,r){this.upsert(e,r,function(){throw"duplicate index"})},t.Vector.prototype.upsert=function(e,r,n){this._magnitude=0;var i=this.positionForIndex(e);this.elements[i]==e?this.elements[i+1]=n(this.elements[i+1],r):this.elements.splice(i,0,e,r)},t.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,r=this.elements.length,n=1;n<r;n+=2){var i=this.elements[n];e+=i*i}return this._magnitude=Math.sqrt(e)},t.Vector.prototype.dot=function(e){for(var r=0,n=this.elements,i=e.elements,s=n.length,o=i.length,a=0,u=0,c=0,d=0;c<s&&d<o;)a=n[c],u=i[d],a<u?c+=2:a>u?d+=2:a==u&&(r+=n[c+1]*i[d+1],c+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r<this.elements.length;r+=2,n++)e[n]=this.elements[r];return e},t.Vector.prototype.toJSON=function(){return this.elements};t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},r={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",s=n+"[^aeiouy]*",o=i+"[aeiou]*",a="^("+s+")?"+o+s,u="^("+s+")?"+o+s+"("+o+")?$",c="^("+s+")?"+o+s+o+s,d="^("+s+")?"+i,y=new RegExp(a),p=new RegExp(c),b=new RegExp(u),m=new RegExp(d),Q=/^(.+?)(ss|i)es$/,f=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,L=/^(.+?)(ed|ing)$/,w=/.$/,k=/(at|bl|iz)$/,O=new RegExp("([^aeiouylsz])\\1$"),j=new RegExp("^"+s+i+"[^aeiouwxy]$"),C=/^(.+?[^aeiou])y$/,A=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,V=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,$=/^(.+?)(s|t)(ion)$/,P=/^(.+?)e$/,N=/ll$/,B=new RegExp("^"+s+i+"[^aeiouwxy]$"),M=function(l){var v,I,E,h,x,T,F;if(l.length<3)return l;if(E=l.substr(0,1),E=="y"&&(l=E.toUpperCase()+l.substr(1)),h=Q,x=f,h.test(l)?l=l.replace(h,"$1$2"):x.test(l)&&(l=l.replace(x,"$1$2")),h=g,x=L,h.test(l)){var S=h.exec(l);h=y,h.test(S[1])&&(h=w,l=l.replace(h,""))}else if(x.test(l)){var S=x.exec(l);v=S[1],x=m,x.test(v)&&(l=v,x=k,T=O,F=j,x.test(l)?l=l+"e":T.test(l)?(h=w,l=l.replace(h,"")):F.test(l)&&(l=l+"e"))}if(h=C,h.test(l)){var S=h.exec(l);v=S[1],l=v+"i"}if(h=A,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+e[I])}if(h=V,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+r[I])}if(h=D,x=$,h.test(l)){var S=h.exec(l);v=S[1],h=p,h.test(v)&&(l=v)}else if(x.test(l)){var S=x.exec(l);v=S[1]+S[2],x=p,x.test(v)&&(l=v)}if(h=P,h.test(l)){var S=h.exec(l);v=S[1],h=p,x=b,T=B,(h.test(v)||x.test(v)&&!T.test(v))&&(l=v)}return h=N,x=p,h.test(l)&&x.test(l)&&(h=w,l=l.replace(h,"")),E=="y"&&(l=E.toLowerCase()+l.substr(1)),l};return function(_){return _.update(M)}}(),t.Pipeline.registerFunction(t.stemmer,"stemmer");t.generateStopWordFilter=function(e){var r=e.reduce(function(n,i){return n[i]=i,n},{});return function(n){if(n&&r[n.toString()]!==n.toString())return n}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter");t.trimmer=function(e){return e.update(function(r){return r.replace(/^\W+/,"").replace(/\W+$/,"")})},t.Pipeline.registerFunction(t.trimmer,"trimmer");t.TokenSet=function(){this.final=!1,this.edges={},this.id=t.TokenSet._nextId,t.TokenSet._nextId+=1},t.TokenSet._nextId=1,t.TokenSet.fromArray=function(e){for(var r=new t.TokenSet.Builder,n=0,i=e.length;n<i;n++)r.insert(e[n]);return r.finish(),r.root},t.TokenSet.fromClause=function(e){return"editDistance"in e?t.TokenSet.fromFuzzyString(e.term,e.editDistance):t.TokenSet.fromString(e.term)},t.TokenSet.fromFuzzyString=function(e,r){for(var n=new t.TokenSet,i=[{node:n,editsRemaining:r,str:e}];i.length;){var s=i.pop();if(s.str.length>0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),y=s.str.charAt(1),p;y in s.node.edges?p=s.node.edges[y]:(p=new t.TokenSet,s.node.edges[y]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i<s;i++){var o=e[i],a=i==s-1;if(o=="*")r.edges[o]=r,r.final=a;else{var u=new t.TokenSet;u.final=a,r.edges[o]=u,r=u}}return n},t.TokenSet.prototype.toArray=function(){for(var e=[],r=[{prefix:"",node:this}];r.length;){var n=r.pop(),i=Object.keys(n.node.edges),s=i.length;n.node.final&&(n.prefix.charAt(0),e.push(n.prefix));for(var o=0;o<s;o++){var a=i[o];r.push({prefix:n.prefix.concat(a),node:n.node.edges[a]})}}return e},t.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",r=Object.keys(this.edges).sort(),n=r.length,i=0;i<n;i++){var s=r[i],o=this.edges[s];e=e+s+o.id}return e},t.TokenSet.prototype.intersect=function(e){for(var r=new t.TokenSet,n=void 0,i=[{qNode:e,output:r,node:this}];i.length;){n=i.pop();for(var s=Object.keys(n.qNode.edges),o=s.length,a=Object.keys(n.node.edges),u=a.length,c=0;c<o;c++)for(var d=s[c],y=0;y<u;y++){var p=a[y];if(p==d||d=="*"){var b=n.node.edges[p],m=n.qNode.edges[d],Q=b.final&&m.final,f=void 0;p in n.output.edges?(f=n.output.edges[p],f.final=f.final||Q):(f=new t.TokenSet,f.final=Q,n.output.edges[p]=f),i.push({qNode:m,output:f,node:b})}}}return r},t.TokenSet.Builder=function(){this.previousWord="",this.root=new t.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},t.TokenSet.Builder.prototype.insert=function(e){var r,n=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var i=0;i<e.length&&i<this.previousWord.length&&e[i]==this.previousWord[i];i++)n++;this.minimize(n),this.uncheckedNodes.length==0?r=this.root:r=this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(var i=n;i<e.length;i++){var s=new t.TokenSet,o=e[i];r.edges[o]=s,this.uncheckedNodes.push({parent:r,char:o,child:s}),r=s}r.final=!0,this.previousWord=e},t.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},t.TokenSet.Builder.prototype.minimize=function(e){for(var r=this.uncheckedNodes.length-1;r>=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u<this.fields.length;u++)i[this.fields[u]]=new t.Vector;e.call(r,r);for(var u=0;u<r.clauses.length;u++){var c=r.clauses[u],d=null,y=t.Set.empty;c.usePipeline?d=this.pipeline.runString(c.term,{fields:c.fields}):d=[c.term];for(var p=0;p<d.length;p++){var b=d[p];c.term=b;var m=t.TokenSet.fromClause(c),Q=this.tokenSet.intersect(m).toArray();if(Q.length===0&&c.presence===t.Query.presence.REQUIRED){for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=t.Set.empty}break}for(var L=0;L<Q.length;L++)for(var w=Q[L],k=this.invertedIndex[w],O=k._index,f=0;f<c.fields.length;f++){var g=c.fields[f],j=k[g],C=Object.keys(j),A=w+"/"+g,V=new t.Set(C);if(c.presence==t.Query.presence.REQUIRED&&(y=y.union(V),o[g]===void 0&&(o[g]=t.Set.complete)),c.presence==t.Query.presence.PROHIBITED){a[g]===void 0&&(a[g]=t.Set.empty),a[g]=a[g].union(V);continue}if(i[g].upsert(O,c.boost,function(ue,ce){return ue+ce}),!s[A]){for(var D=0;D<C.length;D++){var $=C[D],P=new t.FieldRef($,g),N=j[$],B;(B=n[P])===void 0?n[P]=new t.MatchData(w,g,N):B.add(w,g,N)}s[A]=!0}}}if(c.presence===t.Query.presence.REQUIRED)for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=o[g].intersect(y)}}for(var M=t.Set.complete,_=t.Set.empty,u=0;u<this.fields.length;u++){var g=this.fields[u];o[g]&&(M=M.intersect(o[g])),a[g]&&(_=_.union(a[g]))}var l=Object.keys(n),v=[],I=Object.create(null);if(r.isNegated()){l=Object.keys(this.fieldVectors);for(var u=0;u<l.length;u++){var P=l[u],E=t.FieldRef.fromString(P);n[P]=new t.MatchData}}for(var u=0;u<l.length;u++){var E=t.FieldRef.fromString(l[u]),h=E.docRef;if(!!M.contains(h)&&!_.contains(h)){var x=this.fieldVectors[E],T=i[E.fieldName].similarity(x),F;if((F=I[h])!==void 0)F.score+=T,F.matchData.combine(n[E]);else{var S={ref:h,score:T,matchData:n[E]};I[h]=S,v.push(S)}}}return v.sort(function(oe,ae){return ae.score-oe.score})},t.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map(function(n){return[n,this.invertedIndex[n]]},this),r=Object.keys(this.fieldVectors).map(function(n){return[n,this.fieldVectors[n].toJSON()]},this);return{version:t.version,fields:this.fields,fieldVectors:r,invertedIndex:e,pipeline:this.pipeline.toJSON()}},t.Index.load=function(e){var r={},n={},i=e.fieldVectors,s=Object.create(null),o=e.invertedIndex,a=new t.TokenSet.Builder,u=t.Pipeline.load(e.pipeline);e.version!=t.version&&t.utils.warn("Version mismatch when loading serialised index. Current version of lunr '"+t.version+"' does not match serialized index '"+e.version+"'");for(var c=0;c<i.length;c++){var d=i[c],y=d[0],p=d[1];n[y]=new t.Vector(p)}for(var c=0;c<o.length;c++){var d=o[c],b=d[0],m=d[1];a.insert(b),s[b]=m}return a.finish(),r.fields=e.fields,r.fieldVectors=n,r.invertedIndex=s,r.tokenSet=a.root,r.pipeline=u,new t.Index(r)};t.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=t.tokenizer,this.pipeline=new t.Pipeline,this.searchPipeline=new t.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},t.Builder.prototype.ref=function(e){this._ref=e},t.Builder.prototype.field=function(e,r){if(/\//.test(e))throw new RangeError("Field '"+e+"' contains illegal character '/'");this._fields[e]=r||{}},t.Builder.prototype.b=function(e){e<0?this._b=0:e>1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s<i.length;s++){var o=i[s],a=this._fields[o].extractor,u=a?a(e):e[o],c=this.tokenizer(u,{fields:[o]}),d=this.pipeline.run(c),y=new t.FieldRef(n,o),p=Object.create(null);this.fieldTermFrequencies[y]=p,this.fieldLengths[y]=0,this.fieldLengths[y]+=d.length;for(var b=0;b<d.length;b++){var m=d[b];if(p[m]==null&&(p[m]=0),p[m]+=1,this.invertedIndex[m]==null){var Q=Object.create(null);Q._index=this.termIndex,this.termIndex+=1;for(var f=0;f<i.length;f++)Q[i[f]]=Object.create(null);this.invertedIndex[m]=Q}this.invertedIndex[m][o][n]==null&&(this.invertedIndex[m][o][n]=Object.create(null));for(var g=0;g<this.metadataWhitelist.length;g++){var L=this.metadataWhitelist[g],w=m.metadata[L];this.invertedIndex[m][o][n][L]==null&&(this.invertedIndex[m][o][n][L]=[]),this.invertedIndex[m][o][n][L].push(w)}}}},t.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),r=e.length,n={},i={},s=0;s<r;s++){var o=t.FieldRef.fromString(e[s]),a=o.fieldName;i[a]||(i[a]=0),i[a]+=1,n[a]||(n[a]=0),n[a]+=this.fieldLengths[o]}for(var u=Object.keys(this._fields),s=0;s<u.length;s++){var c=u[s];n[c]=n[c]/i[c]}this.averageFieldLength=n},t.Builder.prototype.createFieldVectors=function(){for(var e={},r=Object.keys(this.fieldTermFrequencies),n=r.length,i=Object.create(null),s=0;s<n;s++){for(var o=t.FieldRef.fromString(r[s]),a=o.fieldName,u=this.fieldLengths[o],c=new t.Vector,d=this.fieldTermFrequencies[o],y=Object.keys(d),p=y.length,b=this._fields[a].boost||1,m=this._documents[o.docRef].boost||1,Q=0;Q<p;Q++){var f=y[Q],g=d[f],L=this.invertedIndex[f]._index,w,k,O;i[f]===void 0?(w=t.idf(this.invertedIndex[f],this.documentCount),i[f]=w):w=i[f],k=w*((this._k1+1)*g)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[a]))+g),k*=b,k*=m,O=Math.round(k*1e3)/1e3,c.insert(L,O)}e[o]=c}this.fieldVectors=e},t.Builder.prototype.createTokenSet=function(){this.tokenSet=t.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},t.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new t.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},t.Builder.prototype.use=function(e){var r=Array.prototype.slice.call(arguments,1);r.unshift(this),e.apply(this,r)},t.MatchData=function(e,r,n){for(var i=Object.create(null),s=Object.keys(n||{}),o=0;o<s.length;o++){var a=s[o];i[a]=n[a].slice()}this.metadata=Object.create(null),e!==void 0&&(this.metadata[e]=Object.create(null),this.metadata[e][r]=i)},t.MatchData.prototype.combine=function(e){for(var r=Object.keys(e.metadata),n=0;n<r.length;n++){var i=r[n],s=Object.keys(e.metadata[i]);this.metadata[i]==null&&(this.metadata[i]=Object.create(null));for(var o=0;o<s.length;o++){var a=s[o],u=Object.keys(e.metadata[i][a]);this.metadata[i][a]==null&&(this.metadata[i][a]=Object.create(null));for(var c=0;c<u.length;c++){var d=u[c];this.metadata[i][a][d]==null?this.metadata[i][a][d]=e.metadata[i][a][d]:this.metadata[i][a][d]=this.metadata[i][a][d].concat(e.metadata[i][a][d])}}}},t.MatchData.prototype.add=function(e,r,n){if(!(e in this.metadata)){this.metadata[e]=Object.create(null),this.metadata[e][r]=n;return}if(!(r in this.metadata[e])){this.metadata[e][r]=n;return}for(var i=Object.keys(n),s=0;s<i.length;s++){var o=i[s];o in this.metadata[e][r]?this.metadata[e][r][o]=this.metadata[e][r][o].concat(n[o]):this.metadata[e][r][o]=n[o]}},t.Query=function(e){this.clauses=[],this.allFields=e},t.Query.wildcard=new String("*"),t.Query.wildcard.NONE=0,t.Query.wildcard.LEADING=1,t.Query.wildcard.TRAILING=2,t.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},t.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=t.Query.wildcard.NONE),e.wildcard&t.Query.wildcard.LEADING&&e.term.charAt(0)!=t.Query.wildcard&&(e.term="*"+e.term),e.wildcard&t.Query.wildcard.TRAILING&&e.term.slice(-1)!=t.Query.wildcard&&(e.term=""+e.term+"*"),"presence"in e||(e.presence=t.Query.presence.OPTIONAL),this.clauses.push(e),this},t.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=t.Query.presence.PROHIBITED)return!1;return!0},t.Query.prototype.term=function(e,r){if(Array.isArray(e))return e.forEach(function(i){this.term(i,t.utils.clone(r))},this),this;var n=r||{};return n.term=e.toString(),this.clause(n),this},t.QueryParseError=function(e,r,n){this.name="QueryParseError",this.message=e,this.start=r,this.end=n},t.QueryParseError.prototype=new Error,t.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},t.QueryLexer.prototype.run=function(){for(var e=t.QueryLexer.lexText;e;)e=e(this)},t.QueryLexer.prototype.sliceString=function(){for(var e=[],r=this.start,n=this.pos,i=0;i<this.escapeCharPositions.length;i++)n=this.escapeCharPositions[i],e.push(this.str.slice(r,n)),r=n+1;return e.push(this.str.slice(r,this.pos)),this.escapeCharPositions.length=0,e.join("")},t.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},t.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},t.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos<this.length},t.QueryLexer.EOS="EOS",t.QueryLexer.FIELD="FIELD",t.QueryLexer.TERM="TERM",t.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",t.QueryLexer.BOOST="BOOST",t.QueryLexer.PRESENCE="PRESENCE",t.QueryLexer.lexField=function(e){return e.backup(),e.emit(t.QueryLexer.FIELD),e.ignore(),t.QueryLexer.lexText},t.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof G=="object"?J.exports=r():e.lunr=r()}(this,function(){return t})})()});var K=q((we,Z)=>{"use strict";var me=/["'&<>]/;Z.exports=ve;function ve(t){var e=""+t,r=me.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s<e.length;s++){switch(e.charCodeAt(s)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}o!==s&&(i+=e.substring(o,s)),o=s+1,i+=n}return o!==s?i+e.substring(o,s):i}});var se=Y(X());var ee=Y(K());function te(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=ee.default(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let c=e.get(i);r.has(c)?e.set(o,{location:o,title:a,text:u,parent:c}):(c.title=n.title,c.text=u,r.add(c))}else e.set(o,{location:o,title:a,text:u})}return e}function re(t){let e=new RegExp(t.separator,"img"),r=(n,i,s)=>`${i}<mark data-md-highlight>${s}</mark>`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${t.separator})(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(e,"|")})`,"img");return s=>s.replace(i,r).replace(/<\/mark>(\s+)<mark[^>]*>/img,"$1")}}function ne(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ie(t,e){let r=new Set(t),n={};for(let i=0;i<e.length;i++)for(let s of r)e[i].startsWith(s.term)&&(n[s.term]=!0,r.delete(s));for(let i of r)n[i.term]=!1;return n}function xe(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var W=class{constructor({config:e,docs:r,pipeline:n,index:i}){this.documents=te(r),this.highlight=re(e),lunr.tokenizer.separator=new RegExp(e.separator),typeof i=="undefined"?this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let s=xe(["trimmer","stopWordFilter","stemmer"],n);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.field("title",{boost:1e3}),this.field("text"),this.ref("location");for(let o of r)this.add(o)}):this.index=lunr.Index.load(i)}search(e){if(e)try{let r=this.highlight(e),n=ne(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED);return[...this.index.search(`${e}*`).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.documents.get(o);if(typeof c!="undefined"){let{location:d,title:y,text:p,parent:b}=c,m=ie(n,Object.keys(u.metadata)),Q=+!b+ +Object.values(m).every(f=>f);s.push({location:d,title:r(y),text:r(p),score:a*(1+Q),terms:m})}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.documents.get(o.location);if(typeof a!="undefined"){let u="parent"in a?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map).values()]}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return[]}};var R;(function(t){t[t.SETUP=0]="SETUP",t[t.READY=1]="READY",t[t.QUERY=2]="QUERY",t[t.RESULT=3]="RESULT"})(R||(R={}));var H;function Se(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang)n==="ja"&&r.push(`${e}/tinyseg.js`),n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`);t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Qe(t){return z(this,null,function*(){switch(t.type){case R.SETUP:return yield Se(t.data.config),H=new W(t.data),{type:R.READY};case R.QUERY:return{type:R.RESULT,data:H?H.search(t.data):[]};default:throw new TypeError("Invalid message type")}})}self.lunr=se.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Qe(t.data))}));})();
-/*!
- * escape-html
- * Copyright(c) 2012-2013 TJ Holowaychuk
- * Copyright(c) 2015 Andreas Lubbe
- * Copyright(c) 2015 Tiancheng "Timothy" Gu
- * MIT Licensed
- */
-/*!
- * lunr.Builder
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Index
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Pipeline
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Set
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.TokenSet
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Vector
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.stemmer
- * Copyright (C) 2020 Oliver Nightingale
- * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
- */
-/*!
- * lunr.stopWordFilter
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.tokenizer
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.trimmer
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.utils
- * Copyright (C) 2020 Oliver Nightingale
- */
-/**
- * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
- * Copyright (C) 2020 Oliver Nightingale
- * @license MIT
- */
-//# sourceMappingURL=search.fb4a9340.min.js.map
-
diff --git a/5.4/assets/javascripts/workers/search.fb4a9340.min.js.map b/5.4/assets/javascripts/workers/search.fb4a9340.min.js.map
deleted file mode 100644 (file)
index f7ea053..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "version": 3,
-  "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts"],
-  "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n *   this.field('title')\n *   this.field('body')\n *   this.ref('id')\n *\n *   documents.forEach(function (doc) {\n *     this.add(doc)\n *   }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n  var builder = new lunr.Builder\n\n  builder.pipeline.add(\n    lunr.trimmer,\n    lunr.stopWordFilter,\n    lunr.stemmer\n  )\n\n  builder.searchPipeline.add(\n    lunr.stemmer\n  )\n\n  config.call(builder, builder)\n  return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n  /* eslint-disable no-console */\n  return function (message) {\n    if (global.console && console.warn) {\n      console.warn(message)\n    }\n  }\n  /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n  if (obj === void 0 || obj === null) {\n    return \"\"\n  } else {\n    return obj.toString()\n  }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n  if (obj === null || obj === undefined) {\n    return obj\n  }\n\n  var clone = Object.create(null),\n      keys = Object.keys(obj)\n\n  for (var i = 0; i < keys.length; i++) {\n    var key = keys[i],\n        val = obj[key]\n\n    if (Array.isArray(val)) {\n      clone[key] = val.slice()\n      continue\n    }\n\n    if (typeof val === 'string' ||\n        typeof val === 'number' ||\n        typeof val === 'boolean') {\n      clone[key] = val\n      continue\n    }\n\n    throw new TypeError(\"clone is not deep and does not support nested objects\")\n  }\n\n  return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n  this.docRef = docRef\n  this.fieldName = fieldName\n  this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n  var n = s.indexOf(lunr.FieldRef.joiner)\n\n  if (n === -1) {\n    throw \"malformed field ref string\"\n  }\n\n  var fieldRef = s.slice(0, n),\n      docRef = s.slice(n + 1)\n\n  return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n  if (this._stringValue == undefined) {\n    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n  }\n\n  return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n  this.elements = Object.create(null)\n\n  if (elements) {\n    this.length = elements.length\n\n    for (var i = 0; i < this.length; i++) {\n      this.elements[elements[i]] = true\n    }\n  } else {\n    this.length = 0\n  }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n  intersect: function (other) {\n    return other\n  },\n\n  union: function () {\n    return this\n  },\n\n  contains: function () {\n    return true\n  }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n  intersect: function () {\n    return this\n  },\n\n  union: function (other) {\n    return other\n  },\n\n  contains: function () {\n    return false\n  }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n  return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n  var a, b, elements, intersection = []\n\n  if (other === lunr.Set.complete) {\n    return this\n  }\n\n  if (other === lunr.Set.empty) {\n    return other\n  }\n\n  if (this.length < other.length) {\n    a = this\n    b = other\n  } else {\n    a = other\n    b = this\n  }\n\n  elements = Object.keys(a.elements)\n\n  for (var i = 0; i < elements.length; i++) {\n    var element = elements[i]\n    if (element in b.elements) {\n      intersection.push(element)\n    }\n  }\n\n  return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n  if (other === lunr.Set.complete) {\n    return lunr.Set.complete\n  }\n\n  if (other === lunr.Set.empty) {\n    return this\n  }\n\n  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n  var documentsWithTerm = 0\n\n  for (var fieldName in posting) {\n    if (fieldName == '_index') continue // Ignore the term index, its not a field\n    documentsWithTerm += Object.keys(posting[fieldName]).length\n  }\n\n  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n  return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n  this.str = str || \"\"\n  this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n  return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n *   return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n  this.str = fn(this.str, this.metadata)\n  return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n  fn = fn || function (s) { return s }\n  return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n  if (obj == null || obj == undefined) {\n    return []\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.map(function (t) {\n      return new lunr.Token(\n        lunr.utils.asString(t).toLowerCase(),\n        lunr.utils.clone(metadata)\n      )\n    })\n  }\n\n  var str = obj.toString().toLowerCase(),\n      len = str.length,\n      tokens = []\n\n  for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n    var char = str.charAt(sliceEnd),\n        sliceLength = sliceEnd - sliceStart\n\n    if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n      if (sliceLength > 0) {\n        var tokenMetadata = lunr.utils.clone(metadata) || {}\n        tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n        tokenMetadata[\"index\"] = tokens.length\n\n        tokens.push(\n          new lunr.Token (\n            str.slice(sliceStart, sliceEnd),\n            tokenMetadata\n          )\n        )\n      }\n\n      sliceStart = sliceEnd + 1\n    }\n\n  }\n\n  return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n  this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n  if (label in this.registeredFunctions) {\n    lunr.utils.warn('Overwriting existing registered function: ' + label)\n  }\n\n  fn.label = label\n  lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n  var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n  if (!isRegistered) {\n    lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n  }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n  var pipeline = new lunr.Pipeline\n\n  serialised.forEach(function (fnName) {\n    var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n    if (fn) {\n      pipeline.add(fn)\n    } else {\n      throw new Error('Cannot load unregistered function: ' + fnName)\n    }\n  })\n\n  return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n  var fns = Array.prototype.slice.call(arguments)\n\n  fns.forEach(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n    this._stack.push(fn)\n  }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  pos = pos + 1\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n  var pos = this._stack.indexOf(fn)\n  if (pos == -1) {\n    return\n  }\n\n  this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n  var stackLength = this._stack.length\n\n  for (var i = 0; i < stackLength; i++) {\n    var fn = this._stack[i]\n    var memo = []\n\n    for (var j = 0; j < tokens.length; j++) {\n      var result = fn(tokens[j], j, tokens)\n\n      if (result === null || result === void 0 || result === '') continue\n\n      if (Array.isArray(result)) {\n        for (var k = 0; k < result.length; k++) {\n          memo.push(result[k])\n        }\n      } else {\n        memo.push(result)\n      }\n    }\n\n    tokens = memo\n  }\n\n  return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n  var token = new lunr.Token (str, metadata)\n\n  return this.run([token]).map(function (t) {\n    return t.toString()\n  })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n  this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n  return this._stack.map(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n    return fn.label\n  })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n  this._magnitude = 0\n  this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n  // For an empty vector the tuple can be inserted at the beginning\n  if (this.elements.length == 0) {\n    return 0\n  }\n\n  var start = 0,\n      end = this.elements.length / 2,\n      sliceLength = end - start,\n      pivotPoint = Math.floor(sliceLength / 2),\n      pivotIndex = this.elements[pivotPoint * 2]\n\n  while (sliceLength > 1) {\n    if (pivotIndex < index) {\n      start = pivotPoint\n    }\n\n    if (pivotIndex > index) {\n      end = pivotPoint\n    }\n\n    if (pivotIndex == index) {\n      break\n    }\n\n    sliceLength = end - start\n    pivotPoint = start + Math.floor(sliceLength / 2)\n    pivotIndex = this.elements[pivotPoint * 2]\n  }\n\n  if (pivotIndex == index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex > index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex < index) {\n    return (pivotPoint + 1) * 2\n  }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n  this.upsert(insertIdx, val, function () {\n    throw \"duplicate index\"\n  })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n  this._magnitude = 0\n  var position = this.positionForIndex(insertIdx)\n\n  if (this.elements[position] == insertIdx) {\n    this.elements[position + 1] = fn(this.elements[position + 1], val)\n  } else {\n    this.elements.splice(position, 0, insertIdx, val)\n  }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n  if (this._magnitude) return this._magnitude\n\n  var sumOfSquares = 0,\n      elementsLength = this.elements.length\n\n  for (var i = 1; i < elementsLength; i += 2) {\n    var val = this.elements[i]\n    sumOfSquares += val * val\n  }\n\n  return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n  var dotProduct = 0,\n      a = this.elements, b = otherVector.elements,\n      aLen = a.length, bLen = b.length,\n      aVal = 0, bVal = 0,\n      i = 0, j = 0\n\n  while (i < aLen && j < bLen) {\n    aVal = a[i], bVal = b[j]\n    if (aVal < bVal) {\n      i += 2\n    } else if (aVal > bVal) {\n      j += 2\n    } else if (aVal == bVal) {\n      dotProduct += a[i + 1] * b[j + 1]\n      i += 2\n      j += 2\n    }\n  }\n\n  return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n  return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n  var output = new Array (this.elements.length / 2)\n\n  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n    output[j] = this.elements[i]\n  }\n\n  return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n  return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n  var step2list = {\n      \"ational\" : \"ate\",\n      \"tional\" : \"tion\",\n      \"enci\" : \"ence\",\n      \"anci\" : \"ance\",\n      \"izer\" : \"ize\",\n      \"bli\" : \"ble\",\n      \"alli\" : \"al\",\n      \"entli\" : \"ent\",\n      \"eli\" : \"e\",\n      \"ousli\" : \"ous\",\n      \"ization\" : \"ize\",\n      \"ation\" : \"ate\",\n      \"ator\" : \"ate\",\n      \"alism\" : \"al\",\n      \"iveness\" : \"ive\",\n      \"fulness\" : \"ful\",\n      \"ousness\" : \"ous\",\n      \"aliti\" : \"al\",\n      \"iviti\" : \"ive\",\n      \"biliti\" : \"ble\",\n      \"logi\" : \"log\"\n    },\n\n    step3list = {\n      \"icate\" : \"ic\",\n      \"ative\" : \"\",\n      \"alize\" : \"al\",\n      \"iciti\" : \"ic\",\n      \"ical\" : \"ic\",\n      \"ful\" : \"\",\n      \"ness\" : \"\"\n    },\n\n    c = \"[^aeiou]\",          // consonant\n    v = \"[aeiouy]\",          // vowel\n    C = c + \"[^aeiouy]*\",    // consonant sequence\n    V = v + \"[aeiou]*\",      // vowel sequence\n\n    mgr0 = \"^(\" + C + \")?\" + V + C,               // [C]VC... is m>0\n    meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\",  // [C]VC[V] is m=1\n    mgr1 = \"^(\" + C + \")?\" + V + C + V + C,       // [C]VCVC... is m>1\n    s_v = \"^(\" + C + \")?\" + v;                   // vowel in stem\n\n  var re_mgr0 = new RegExp(mgr0);\n  var re_mgr1 = new RegExp(mgr1);\n  var re_meq1 = new RegExp(meq1);\n  var re_s_v = new RegExp(s_v);\n\n  var re_1a = /^(.+?)(ss|i)es$/;\n  var re2_1a = /^(.+?)([^s])s$/;\n  var re_1b = /^(.+?)eed$/;\n  var re2_1b = /^(.+?)(ed|ing)$/;\n  var re_1b_2 = /.$/;\n  var re2_1b_2 = /(at|bl|iz)$/;\n  var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n  var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var re_1c = /^(.+?[^aeiou])y$/;\n  var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n  var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n  var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n  var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n  var re_5 = /^(.+?)e$/;\n  var re_5_1 = /ll$/;\n  var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var porterStemmer = function porterStemmer(w) {\n    var stem,\n      suffix,\n      firstch,\n      re,\n      re2,\n      re3,\n      re4;\n\n    if (w.length < 3) { return w; }\n\n    firstch = w.substr(0,1);\n    if (firstch == \"y\") {\n      w = firstch.toUpperCase() + w.substr(1);\n    }\n\n    // Step 1a\n    re = re_1a\n    re2 = re2_1a;\n\n    if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n    else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n    // Step 1b\n    re = re_1b;\n    re2 = re2_1b;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      re = re_mgr0;\n      if (re.test(fp[1])) {\n        re = re_1b_2;\n        w = w.replace(re,\"\");\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1];\n      re2 = re_s_v;\n      if (re2.test(stem)) {\n        w = stem;\n        re2 = re2_1b_2;\n        re3 = re3_1b_2;\n        re4 = re4_1b_2;\n        if (re2.test(w)) { w = w + \"e\"; }\n        else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n        else if (re4.test(w)) { w = w + \"e\"; }\n      }\n    }\n\n    // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n    re = re_1c;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      w = stem + \"i\";\n    }\n\n    // Step 2\n    re = re_2;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step2list[suffix];\n      }\n    }\n\n    // Step 3\n    re = re_3;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step3list[suffix];\n      }\n    }\n\n    // Step 4\n    re = re_4;\n    re2 = re2_4;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      if (re.test(stem)) {\n        w = stem;\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1] + fp[2];\n      re2 = re_mgr1;\n      if (re2.test(stem)) {\n        w = stem;\n      }\n    }\n\n    // Step 5\n    re = re_5;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      re2 = re_meq1;\n      re3 = re3_5;\n      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n        w = stem;\n      }\n    }\n\n    re = re_5_1;\n    re2 = re_mgr1;\n    if (re.test(w) && re2.test(w)) {\n      re = re_1b_2;\n      w = w.replace(re,\"\");\n    }\n\n    // and turn initial Y back to y\n\n    if (firstch == \"y\") {\n      w = firstch.toLowerCase() + w.substr(1);\n    }\n\n    return w;\n  };\n\n  return function (token) {\n    return token.update(porterStemmer);\n  }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n  var words = stopWords.reduce(function (memo, stopWord) {\n    memo[stopWord] = stopWord\n    return memo\n  }, {})\n\n  return function (token) {\n    if (token && words[token.toString()] !== token.toString()) return token\n  }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n  'a',\n  'able',\n  'about',\n  'across',\n  'after',\n  'all',\n  'almost',\n  'also',\n  'am',\n  'among',\n  'an',\n  'and',\n  'any',\n  'are',\n  'as',\n  'at',\n  'be',\n  'because',\n  'been',\n  'but',\n  'by',\n  'can',\n  'cannot',\n  'could',\n  'dear',\n  'did',\n  'do',\n  'does',\n  'either',\n  'else',\n  'ever',\n  'every',\n  'for',\n  'from',\n  'get',\n  'got',\n  'had',\n  'has',\n  'have',\n  'he',\n  'her',\n  'hers',\n  'him',\n  'his',\n  'how',\n  'however',\n  'i',\n  'if',\n  'in',\n  'into',\n  'is',\n  'it',\n  'its',\n  'just',\n  'least',\n  'let',\n  'like',\n  'likely',\n  'may',\n  'me',\n  'might',\n  'most',\n  'must',\n  'my',\n  'neither',\n  'no',\n  'nor',\n  'not',\n  'of',\n  'off',\n  'often',\n  'on',\n  'only',\n  'or',\n  'other',\n  'our',\n  'own',\n  'rather',\n  'said',\n  'say',\n  'says',\n  'she',\n  'should',\n  'since',\n  'so',\n  'some',\n  'than',\n  'that',\n  'the',\n  'their',\n  'them',\n  'then',\n  'there',\n  'these',\n  'they',\n  'this',\n  'tis',\n  'to',\n  'too',\n  'twas',\n  'us',\n  'wants',\n  'was',\n  'we',\n  'were',\n  'what',\n  'when',\n  'where',\n  'which',\n  'while',\n  'who',\n  'whom',\n  'why',\n  'will',\n  'with',\n  'would',\n  'yet',\n  'you',\n  'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n  return token.update(function (s) {\n    return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n  })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n  this.final = false\n  this.edges = {}\n  this.id = lunr.TokenSet._nextId\n  lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n  var builder = new lunr.TokenSet.Builder\n\n  for (var i = 0, len = arr.length; i < len; i++) {\n    builder.insert(arr[i])\n  }\n\n  builder.finish()\n  return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n  if ('editDistance' in clause) {\n    return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n  } else {\n    return lunr.TokenSet.fromString(clause.term)\n  }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n  var root = new lunr.TokenSet\n\n  var stack = [{\n    node: root,\n    editsRemaining: editDistance,\n    str: str\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop()\n\n    // no edit\n    if (frame.str.length > 0) {\n      var char = frame.str.charAt(0),\n          noEditNode\n\n      if (char in frame.node.edges) {\n        noEditNode = frame.node.edges[char]\n      } else {\n        noEditNode = new lunr.TokenSet\n        frame.node.edges[char] = noEditNode\n      }\n\n      if (frame.str.length == 1) {\n        noEditNode.final = true\n      }\n\n      stack.push({\n        node: noEditNode,\n        editsRemaining: frame.editsRemaining,\n        str: frame.str.slice(1)\n      })\n    }\n\n    if (frame.editsRemaining == 0) {\n      continue\n    }\n\n    // insertion\n    if (\"*\" in frame.node.edges) {\n      var insertionNode = frame.node.edges[\"*\"]\n    } else {\n      var insertionNode = new lunr.TokenSet\n      frame.node.edges[\"*\"] = insertionNode\n    }\n\n    if (frame.str.length == 0) {\n      insertionNode.final = true\n    }\n\n    stack.push({\n      node: insertionNode,\n      editsRemaining: frame.editsRemaining - 1,\n      str: frame.str\n    })\n\n    // deletion\n    // can only do a deletion if we have enough edits remaining\n    // and if there are characters left to delete in the string\n    if (frame.str.length > 1) {\n      stack.push({\n        node: frame.node,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // deletion\n    // just removing the last character from the str\n    if (frame.str.length == 1) {\n      frame.node.final = true\n    }\n\n    // substitution\n    // can only do a substitution if we have enough edits remaining\n    // and if there are characters left to substitute\n    if (frame.str.length >= 1) {\n      if (\"*\" in frame.node.edges) {\n        var substitutionNode = frame.node.edges[\"*\"]\n      } else {\n        var substitutionNode = new lunr.TokenSet\n        frame.node.edges[\"*\"] = substitutionNode\n      }\n\n      if (frame.str.length == 1) {\n        substitutionNode.final = true\n      }\n\n      stack.push({\n        node: substitutionNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // transposition\n    // can only do a transposition if there are edits remaining\n    // and there are enough characters to transpose\n    if (frame.str.length > 1) {\n      var charA = frame.str.charAt(0),\n          charB = frame.str.charAt(1),\n          transposeNode\n\n      if (charB in frame.node.edges) {\n        transposeNode = frame.node.edges[charB]\n      } else {\n        transposeNode = new lunr.TokenSet\n        frame.node.edges[charB] = transposeNode\n      }\n\n      if (frame.str.length == 1) {\n        transposeNode.final = true\n      }\n\n      stack.push({\n        node: transposeNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: charA + frame.str.slice(2)\n      })\n    }\n  }\n\n  return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n  var node = new lunr.TokenSet,\n      root = node\n\n  /*\n   * Iterates through all characters within the passed string\n   * appending a node for each character.\n   *\n   * When a wildcard character is found then a self\n   * referencing edge is introduced to continually match\n   * any number of any characters.\n   */\n  for (var i = 0, len = str.length; i < len; i++) {\n    var char = str[i],\n        final = (i == len - 1)\n\n    if (char == \"*\") {\n      node.edges[char] = node\n      node.final = final\n\n    } else {\n      var next = new lunr.TokenSet\n      next.final = final\n\n      node.edges[char] = next\n      node = next\n    }\n  }\n\n  return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n  var words = []\n\n  var stack = [{\n    prefix: \"\",\n    node: this\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop(),\n        edges = Object.keys(frame.node.edges),\n        len = edges.length\n\n    if (frame.node.final) {\n      /* In Safari, at this point the prefix is sometimes corrupted, see:\n       * https://github.com/olivernn/lunr.js/issues/279 Calling any\n       * String.prototype method forces Safari to \"cast\" this string to what\n       * it's supposed to be, fixing the bug. */\n      frame.prefix.charAt(0)\n      words.push(frame.prefix)\n    }\n\n    for (var i = 0; i < len; i++) {\n      var edge = edges[i]\n\n      stack.push({\n        prefix: frame.prefix.concat(edge),\n        node: frame.node.edges[edge]\n      })\n    }\n  }\n\n  return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n  // NOTE: Using Object.keys here as this.edges is very likely\n  // to enter 'hash-mode' with many keys being added\n  //\n  // avoiding a for-in loop here as it leads to the function\n  // being de-optimised (at least in V8). From some simple\n  // benchmarks the performance is comparable, but allowing\n  // V8 to optimize may mean easy performance wins in the future.\n\n  if (this._str) {\n    return this._str\n  }\n\n  var str = this.final ? '1' : '0',\n      labels = Object.keys(this.edges).sort(),\n      len = labels.length\n\n  for (var i = 0; i < len; i++) {\n    var label = labels[i],\n        node = this.edges[label]\n\n    str = str + label + node.id\n  }\n\n  return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n  var output = new lunr.TokenSet,\n      frame = undefined\n\n  var stack = [{\n    qNode: b,\n    output: output,\n    node: this\n  }]\n\n  while (stack.length) {\n    frame = stack.pop()\n\n    // NOTE: As with the #toString method, we are using\n    // Object.keys and a for loop instead of a for-in loop\n    // as both of these objects enter 'hash' mode, causing\n    // the function to be de-optimised in V8\n    var qEdges = Object.keys(frame.qNode.edges),\n        qLen = qEdges.length,\n        nEdges = Object.keys(frame.node.edges),\n        nLen = nEdges.length\n\n    for (var q = 0; q < qLen; q++) {\n      var qEdge = qEdges[q]\n\n      for (var n = 0; n < nLen; n++) {\n        var nEdge = nEdges[n]\n\n        if (nEdge == qEdge || qEdge == '*') {\n          var node = frame.node.edges[nEdge],\n              qNode = frame.qNode.edges[qEdge],\n              final = node.final && qNode.final,\n              next = undefined\n\n          if (nEdge in frame.output.edges) {\n            // an edge already exists for this character\n            // no need to create a new node, just set the finality\n            // bit unless this node is already final\n            next = frame.output.edges[nEdge]\n            next.final = next.final || final\n\n          } else {\n            // no edge exists yet, must create one\n            // set the finality bit and insert it\n            // into the output\n            next = new lunr.TokenSet\n            next.final = final\n            frame.output.edges[nEdge] = next\n          }\n\n          stack.push({\n            qNode: qNode,\n            output: next,\n            node: node\n          })\n        }\n      }\n    }\n  }\n\n  return output\n}\nlunr.TokenSet.Builder = function () {\n  this.previousWord = \"\"\n  this.root = new lunr.TokenSet\n  this.uncheckedNodes = []\n  this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n  var node,\n      commonPrefix = 0\n\n  if (word < this.previousWord) {\n    throw new Error (\"Out of order word insertion\")\n  }\n\n  for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n    if (word[i] != this.previousWord[i]) break\n    commonPrefix++\n  }\n\n  this.minimize(commonPrefix)\n\n  if (this.uncheckedNodes.length == 0) {\n    node = this.root\n  } else {\n    node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n  }\n\n  for (var i = commonPrefix; i < word.length; i++) {\n    var nextNode = new lunr.TokenSet,\n        char = word[i]\n\n    node.edges[char] = nextNode\n\n    this.uncheckedNodes.push({\n      parent: node,\n      char: char,\n      child: nextNode\n    })\n\n    node = nextNode\n  }\n\n  node.final = true\n  this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n  this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n  for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n    var node = this.uncheckedNodes[i],\n        childKey = node.child.toString()\n\n    if (childKey in this.minimizedNodes) {\n      node.parent.edges[node.char] = this.minimizedNodes[childKey]\n    } else {\n      // Cache the key for this node since\n      // we know it can't change anymore\n      node.child._str = childKey\n\n      this.minimizedNodes[childKey] = node.child\n    }\n\n    this.uncheckedNodes.pop()\n  }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n  this.invertedIndex = attrs.invertedIndex\n  this.fieldVectors = attrs.fieldVectors\n  this.tokenSet = attrs.tokenSet\n  this.fields = attrs.fields\n  this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example <caption>Simple single term query</caption>\n * hello\n * @example <caption>Multiple term query</caption>\n * hello world\n * @example <caption>term scoped to a field</caption>\n * title:hello\n * @example <caption>term with a boost of 10</caption>\n * hello^10\n * @example <caption>term with an edit distance of 2</caption>\n * hello~2\n * @example <caption>terms with presence modifiers</caption>\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first.  For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n  return this.query(function (query) {\n    var parser = new lunr.QueryParser(queryString, query)\n    parser.parse()\n  })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n  // for each query clause\n  // * process terms\n  // * expand terms from token set\n  // * find matching documents and metadata\n  // * get document vectors\n  // * score documents\n\n  var query = new lunr.Query(this.fields),\n      matchingFields = Object.create(null),\n      queryVectors = Object.create(null),\n      termFieldCache = Object.create(null),\n      requiredMatches = Object.create(null),\n      prohibitedMatches = Object.create(null)\n\n  /*\n   * To support field level boosts a query vector is created per\n   * field. An empty vector is eagerly created to support negated\n   * queries.\n   */\n  for (var i = 0; i < this.fields.length; i++) {\n    queryVectors[this.fields[i]] = new lunr.Vector\n  }\n\n  fn.call(query, query)\n\n  for (var i = 0; i < query.clauses.length; i++) {\n    /*\n     * Unless the pipeline has been disabled for this term, which is\n     * the case for terms with wildcards, we need to pass the clause\n     * term through the search pipeline. A pipeline returns an array\n     * of processed terms. Pipeline functions may expand the passed\n     * term, which means we may end up performing multiple index lookups\n     * for a single query term.\n     */\n    var clause = query.clauses[i],\n        terms = null,\n        clauseMatches = lunr.Set.empty\n\n    if (clause.usePipeline) {\n      terms = this.pipeline.runString(clause.term, {\n        fields: clause.fields\n      })\n    } else {\n      terms = [clause.term]\n    }\n\n    for (var m = 0; m < terms.length; m++) {\n      var term = terms[m]\n\n      /*\n       * Each term returned from the pipeline needs to use the same query\n       * clause object, e.g. the same boost and or edit distance. The\n       * simplest way to do this is to re-use the clause object but mutate\n       * its term property.\n       */\n      clause.term = term\n\n      /*\n       * From the term in the clause we create a token set which will then\n       * be used to intersect the indexes token set to get a list of terms\n       * to lookup in the inverted index\n       */\n      var termTokenSet = lunr.TokenSet.fromClause(clause),\n          expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n      /*\n       * If a term marked as required does not exist in the tokenSet it is\n       * impossible for the search to return any matches. We set all the field\n       * scoped required matches set to empty and stop examining any further\n       * clauses.\n       */\n      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n        for (var k = 0; k < clause.fields.length; k++) {\n          var field = clause.fields[k]\n          requiredMatches[field] = lunr.Set.empty\n        }\n\n        break\n      }\n\n      for (var j = 0; j < expandedTerms.length; j++) {\n        /*\n         * For each term get the posting and termIndex, this is required for\n         * building the query vector.\n         */\n        var expandedTerm = expandedTerms[j],\n            posting = this.invertedIndex[expandedTerm],\n            termIndex = posting._index\n\n        for (var k = 0; k < clause.fields.length; k++) {\n          /*\n           * For each field that this query term is scoped by (by default\n           * all fields are in scope) we need to get all the document refs\n           * that have this term in that field.\n           *\n           * The posting is the entry in the invertedIndex for the matching\n           * term from above.\n           */\n          var field = clause.fields[k],\n              fieldPosting = posting[field],\n              matchingDocumentRefs = Object.keys(fieldPosting),\n              termField = expandedTerm + \"/\" + field,\n              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n          /*\n           * if the presence of this term is required ensure that the matching\n           * documents are added to the set of required matches for this clause.\n           *\n           */\n          if (clause.presence == lunr.Query.presence.REQUIRED) {\n            clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n            if (requiredMatches[field] === undefined) {\n              requiredMatches[field] = lunr.Set.complete\n            }\n          }\n\n          /*\n           * if the presence of this term is prohibited ensure that the matching\n           * documents are added to the set of prohibited matches for this field,\n           * creating that set if it does not yet exist.\n           */\n          if (clause.presence == lunr.Query.presence.PROHIBITED) {\n            if (prohibitedMatches[field] === undefined) {\n              prohibitedMatches[field] = lunr.Set.empty\n            }\n\n            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n            /*\n             * Prohibited matches should not be part of the query vector used for\n             * similarity scoring and no metadata should be extracted so we continue\n             * to the next field\n             */\n            continue\n          }\n\n          /*\n           * The query field vector is populated using the termIndex found for\n           * the term and a unit value with the appropriate boost applied.\n           * Using upsert because there could already be an entry in the vector\n           * for the term we are working with. In that case we just add the scores\n           * together.\n           */\n          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n          /**\n           * If we've already seen this term, field combo then we've already collected\n           * the matching documents and metadata, no need to go through all that again\n           */\n          if (termFieldCache[termField]) {\n            continue\n          }\n\n          for (var l = 0; l < matchingDocumentRefs.length; l++) {\n            /*\n             * All metadata for this term/field/document triple\n             * are then extracted and collected into an instance\n             * of lunr.MatchData ready to be returned in the query\n             * results\n             */\n            var matchingDocumentRef = matchingDocumentRefs[l],\n                matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n                metadata = fieldPosting[matchingDocumentRef],\n                fieldMatch\n\n            if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n              matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n            } else {\n              fieldMatch.add(expandedTerm, field, metadata)\n            }\n\n          }\n\n          termFieldCache[termField] = true\n        }\n      }\n    }\n\n    /**\n     * If the presence was required we need to update the requiredMatches field sets.\n     * We do this after all fields for the term have collected their matches because\n     * the clause terms presence is required in _any_ of the fields not _all_ of the\n     * fields.\n     */\n    if (clause.presence === lunr.Query.presence.REQUIRED) {\n      for (var k = 0; k < clause.fields.length; k++) {\n        var field = clause.fields[k]\n        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n      }\n    }\n  }\n\n  /**\n   * Need to combine the field scoped required and prohibited\n   * matching documents into a global set of required and prohibited\n   * matches\n   */\n  var allRequiredMatches = lunr.Set.complete,\n      allProhibitedMatches = lunr.Set.empty\n\n  for (var i = 0; i < this.fields.length; i++) {\n    var field = this.fields[i]\n\n    if (requiredMatches[field]) {\n      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n    }\n\n    if (prohibitedMatches[field]) {\n      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n    }\n  }\n\n  var matchingFieldRefs = Object.keys(matchingFields),\n      results = [],\n      matches = Object.create(null)\n\n  /*\n   * If the query is negated (contains only prohibited terms)\n   * we need to get _all_ fieldRefs currently existing in the\n   * index. This is only done when we know that the query is\n   * entirely prohibited terms to avoid any cost of getting all\n   * fieldRefs unnecessarily.\n   *\n   * Additionally, blank MatchData must be created to correctly\n   * populate the results.\n   */\n  if (query.isNegated()) {\n    matchingFieldRefs = Object.keys(this.fieldVectors)\n\n    for (var i = 0; i < matchingFieldRefs.length; i++) {\n      var matchingFieldRef = matchingFieldRefs[i]\n      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n      matchingFields[matchingFieldRef] = new lunr.MatchData\n    }\n  }\n\n  for (var i = 0; i < matchingFieldRefs.length; i++) {\n    /*\n     * Currently we have document fields that match the query, but we\n     * need to return documents. The matchData and scores are combined\n     * from multiple fields belonging to the same document.\n     *\n     * Scores are calculated by field, using the query vectors created\n     * above, and combined into a final document score using addition.\n     */\n    var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n        docRef = fieldRef.docRef\n\n    if (!allRequiredMatches.contains(docRef)) {\n      continue\n    }\n\n    if (allProhibitedMatches.contains(docRef)) {\n      continue\n    }\n\n    var fieldVector = this.fieldVectors[fieldRef],\n        score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n        docMatch\n\n    if ((docMatch = matches[docRef]) !== undefined) {\n      docMatch.score += score\n      docMatch.matchData.combine(matchingFields[fieldRef])\n    } else {\n      var match = {\n        ref: docRef,\n        score: score,\n        matchData: matchingFields[fieldRef]\n      }\n      matches[docRef] = match\n      results.push(match)\n    }\n  }\n\n  /*\n   * Sort the results objects by score, highest first.\n   */\n  return results.sort(function (a, b) {\n    return b.score - a.score\n  })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n  var invertedIndex = Object.keys(this.invertedIndex)\n    .sort()\n    .map(function (term) {\n      return [term, this.invertedIndex[term]]\n    }, this)\n\n  var fieldVectors = Object.keys(this.fieldVectors)\n    .map(function (ref) {\n      return [ref, this.fieldVectors[ref].toJSON()]\n    }, this)\n\n  return {\n    version: lunr.version,\n    fields: this.fields,\n    fieldVectors: fieldVectors,\n    invertedIndex: invertedIndex,\n    pipeline: this.pipeline.toJSON()\n  }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n  var attrs = {},\n      fieldVectors = {},\n      serializedVectors = serializedIndex.fieldVectors,\n      invertedIndex = Object.create(null),\n      serializedInvertedIndex = serializedIndex.invertedIndex,\n      tokenSetBuilder = new lunr.TokenSet.Builder,\n      pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n  if (serializedIndex.version != lunr.version) {\n    lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n  }\n\n  for (var i = 0; i < serializedVectors.length; i++) {\n    var tuple = serializedVectors[i],\n        ref = tuple[0],\n        elements = tuple[1]\n\n    fieldVectors[ref] = new lunr.Vector(elements)\n  }\n\n  for (var i = 0; i < serializedInvertedIndex.length; i++) {\n    var tuple = serializedInvertedIndex[i],\n        term = tuple[0],\n        posting = tuple[1]\n\n    tokenSetBuilder.insert(term)\n    invertedIndex[term] = posting\n  }\n\n  tokenSetBuilder.finish()\n\n  attrs.fields = serializedIndex.fields\n\n  attrs.fieldVectors = fieldVectors\n  attrs.invertedIndex = invertedIndex\n  attrs.tokenSet = tokenSetBuilder.root\n  attrs.pipeline = pipeline\n\n  return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n  this._ref = \"id\"\n  this._fields = Object.create(null)\n  this._documents = Object.create(null)\n  this.invertedIndex = Object.create(null)\n  this.fieldTermFrequencies = {}\n  this.fieldLengths = {}\n  this.tokenizer = lunr.tokenizer\n  this.pipeline = new lunr.Pipeline\n  this.searchPipeline = new lunr.Pipeline\n  this.documentCount = 0\n  this._b = 0.75\n  this._k1 = 1.2\n  this.termIndex = 0\n  this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n  this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example <caption>Extracting a nested field</caption>\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n  if (/\\//.test(fieldName)) {\n    throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n  }\n\n  this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n  if (number < 0) {\n    this._b = 0\n  } else if (number > 1) {\n    this._b = 1\n  } else {\n    this._b = number\n  }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n  this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n  var docRef = doc[this._ref],\n      fields = Object.keys(this._fields)\n\n  this._documents[docRef] = attributes || {}\n  this.documentCount += 1\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i],\n        extractor = this._fields[fieldName].extractor,\n        field = extractor ? extractor(doc) : doc[fieldName],\n        tokens = this.tokenizer(field, {\n          fields: [fieldName]\n        }),\n        terms = this.pipeline.run(tokens),\n        fieldRef = new lunr.FieldRef (docRef, fieldName),\n        fieldTerms = Object.create(null)\n\n    this.fieldTermFrequencies[fieldRef] = fieldTerms\n    this.fieldLengths[fieldRef] = 0\n\n    // store the length of this field for this document\n    this.fieldLengths[fieldRef] += terms.length\n\n    // calculate term frequencies for this field\n    for (var j = 0; j < terms.length; j++) {\n      var term = terms[j]\n\n      if (fieldTerms[term] == undefined) {\n        fieldTerms[term] = 0\n      }\n\n      fieldTerms[term] += 1\n\n      // add to inverted index\n      // create an initial posting if one doesn't exist\n      if (this.invertedIndex[term] == undefined) {\n        var posting = Object.create(null)\n        posting[\"_index\"] = this.termIndex\n        this.termIndex += 1\n\n        for (var k = 0; k < fields.length; k++) {\n          posting[fields[k]] = Object.create(null)\n        }\n\n        this.invertedIndex[term] = posting\n      }\n\n      // add an entry for this term/fieldName/docRef to the invertedIndex\n      if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n        this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n      }\n\n      // store all whitelisted metadata about this token in the\n      // inverted index\n      for (var l = 0; l < this.metadataWhitelist.length; l++) {\n        var metadataKey = this.metadataWhitelist[l],\n            metadata = term.metadata[metadataKey]\n\n        if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n          this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n        }\n\n        this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n      }\n    }\n\n  }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n  var fieldRefs = Object.keys(this.fieldLengths),\n      numberOfFields = fieldRefs.length,\n      accumulator = {},\n      documentsWithField = {}\n\n  for (var i = 0; i < numberOfFields; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        field = fieldRef.fieldName\n\n    documentsWithField[field] || (documentsWithField[field] = 0)\n    documentsWithField[field] += 1\n\n    accumulator[field] || (accumulator[field] = 0)\n    accumulator[field] += this.fieldLengths[fieldRef]\n  }\n\n  var fields = Object.keys(this._fields)\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i]\n    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n  }\n\n  this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n  var fieldVectors = {},\n      fieldRefs = Object.keys(this.fieldTermFrequencies),\n      fieldRefsLength = fieldRefs.length,\n      termIdfCache = Object.create(null)\n\n  for (var i = 0; i < fieldRefsLength; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        fieldName = fieldRef.fieldName,\n        fieldLength = this.fieldLengths[fieldRef],\n        fieldVector = new lunr.Vector,\n        termFrequencies = this.fieldTermFrequencies[fieldRef],\n        terms = Object.keys(termFrequencies),\n        termsLength = terms.length\n\n\n    var fieldBoost = this._fields[fieldName].boost || 1,\n        docBoost = this._documents[fieldRef.docRef].boost || 1\n\n    for (var j = 0; j < termsLength; j++) {\n      var term = terms[j],\n          tf = termFrequencies[term],\n          termIndex = this.invertedIndex[term]._index,\n          idf, score, scoreWithPrecision\n\n      if (termIdfCache[term] === undefined) {\n        idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n        termIdfCache[term] = idf\n      } else {\n        idf = termIdfCache[term]\n      }\n\n      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n      score *= fieldBoost\n      score *= docBoost\n      scoreWithPrecision = Math.round(score * 1000) / 1000\n      // Converts 1.23456789 to 1.234.\n      // Reducing the precision so that the vectors take up less\n      // space when serialised. Doing it now so that they behave\n      // the same before and after serialisation. Also, this is\n      // the fastest approach to reducing a number's precision in\n      // JavaScript.\n\n      fieldVector.insert(termIndex, scoreWithPrecision)\n    }\n\n    fieldVectors[fieldRef] = fieldVector\n  }\n\n  this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n  this.tokenSet = lunr.TokenSet.fromArray(\n    Object.keys(this.invertedIndex).sort()\n  )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n  this.calculateAverageFieldLengths()\n  this.createFieldVectors()\n  this.createTokenSet()\n\n  return new lunr.Index({\n    invertedIndex: this.invertedIndex,\n    fieldVectors: this.fieldVectors,\n    tokenSet: this.tokenSet,\n    fields: Object.keys(this._fields),\n    pipeline: this.searchPipeline\n  })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n  var args = Array.prototype.slice.call(arguments, 1)\n  args.unshift(this)\n  fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n  var clonedMetadata = Object.create(null),\n      metadataKeys = Object.keys(metadata || {})\n\n  // Cloning the metadata to prevent the original\n  // being mutated during match data combination.\n  // Metadata is kept in an array within the inverted\n  // index so cloning the data can be done with\n  // Array#slice\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n    clonedMetadata[key] = metadata[key].slice()\n  }\n\n  this.metadata = Object.create(null)\n\n  if (term !== undefined) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = clonedMetadata\n  }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n  var terms = Object.keys(otherMatchData.metadata)\n\n  for (var i = 0; i < terms.length; i++) {\n    var term = terms[i],\n        fields = Object.keys(otherMatchData.metadata[term])\n\n    if (this.metadata[term] == undefined) {\n      this.metadata[term] = Object.create(null)\n    }\n\n    for (var j = 0; j < fields.length; j++) {\n      var field = fields[j],\n          keys = Object.keys(otherMatchData.metadata[term][field])\n\n      if (this.metadata[term][field] == undefined) {\n        this.metadata[term][field] = Object.create(null)\n      }\n\n      for (var k = 0; k < keys.length; k++) {\n        var key = keys[k]\n\n        if (this.metadata[term][field][key] == undefined) {\n          this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n        } else {\n          this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n        }\n\n      }\n    }\n  }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n  if (!(term in this.metadata)) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  if (!(field in this.metadata[term])) {\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  var metadataKeys = Object.keys(metadata)\n\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n\n    if (key in this.metadata[term][field]) {\n      this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n    } else {\n      this.metadata[term][field][key] = metadata[key]\n    }\n  }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n  this.clauses = []\n  this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with trailing wildcard</caption>\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example <caption>query term with leading and trailing wildcard</caption>\n * query.term('foo', {\n *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with required presence</caption>\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n  /**\n   * Term's presence in a document is optional, this is the default value.\n   */\n  OPTIONAL: 1,\n\n  /**\n   * Term's presence in a document is required, documents that do not contain\n   * this term will not be returned.\n   */\n  REQUIRED: 2,\n\n  /**\n   * Term's presence in a document is prohibited, documents that do contain\n   * this term will not be returned.\n   */\n  PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n  if (!('fields' in clause)) {\n    clause.fields = this.allFields\n  }\n\n  if (!('boost' in clause)) {\n    clause.boost = 1\n  }\n\n  if (!('usePipeline' in clause)) {\n    clause.usePipeline = true\n  }\n\n  if (!('wildcard' in clause)) {\n    clause.wildcard = lunr.Query.wildcard.NONE\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n    clause.term = \"*\" + clause.term\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n    clause.term = \"\" + clause.term + \"*\"\n  }\n\n  if (!('presence' in clause)) {\n    clause.presence = lunr.Query.presence.OPTIONAL\n  }\n\n  this.clauses.push(clause)\n\n  return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n  for (var i = 0; i < this.clauses.length; i++) {\n    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n      return false\n    }\n  }\n\n  return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example <caption>adding a single term to a query</caption>\n * query.term(\"foo\")\n * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>\n * query.term(\"foo\", {\n *   fields: [\"title\"],\n *   boost: 10,\n *   wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n  if (Array.isArray(term)) {\n    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n    return this\n  }\n\n  var clause = options || {}\n  clause.term = term.toString()\n\n  this.clause(clause)\n\n  return this\n}\nlunr.QueryParseError = function (message, start, end) {\n  this.name = \"QueryParseError\"\n  this.message = message\n  this.start = start\n  this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n  this.lexemes = []\n  this.str = str\n  this.length = str.length\n  this.pos = 0\n  this.start = 0\n  this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n  var state = lunr.QueryLexer.lexText\n\n  while (state) {\n    state = state(this)\n  }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n  var subSlices = [],\n      sliceStart = this.start,\n      sliceEnd = this.pos\n\n  for (var i = 0; i < this.escapeCharPositions.length; i++) {\n    sliceEnd = this.escapeCharPositions[i]\n    subSlices.push(this.str.slice(sliceStart, sliceEnd))\n    sliceStart = sliceEnd + 1\n  }\n\n  subSlices.push(this.str.slice(sliceStart, this.pos))\n  this.escapeCharPositions.length = 0\n\n  return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n  this.lexemes.push({\n    type: type,\n    str: this.sliceString(),\n    start: this.start,\n    end: this.pos\n  })\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n  this.escapeCharPositions.push(this.pos - 1)\n  this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n  if (this.pos >= this.length) {\n    return lunr.QueryLexer.EOS\n  }\n\n  var char = this.str.charAt(this.pos)\n  this.pos += 1\n  return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n  return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n  if (this.start == this.pos) {\n    this.pos += 1\n  }\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n  this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n  var char, charCode\n\n  do {\n    char = this.next()\n    charCode = char.charCodeAt(0)\n  } while (charCode > 47 && charCode < 58)\n\n  if (char != lunr.QueryLexer.EOS) {\n    this.backup()\n  }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n  return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n  lexer.backup()\n  lexer.emit(lunr.QueryLexer.FIELD)\n  lexer.ignore()\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n  if (lexer.width() > 1) {\n    lexer.backup()\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n\n  lexer.ignore()\n\n  if (lexer.more()) {\n    return lunr.QueryLexer.lexText\n  }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.BOOST)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n  if (lexer.width() > 0) {\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n  while (true) {\n    var char = lexer.next()\n\n    if (char == lunr.QueryLexer.EOS) {\n      return lunr.QueryLexer.lexEOS\n    }\n\n    // Escape character is '\\'\n    if (char.charCodeAt(0) == 92) {\n      lexer.escapeCharacter()\n      continue\n    }\n\n    if (char == \":\") {\n      return lunr.QueryLexer.lexField\n    }\n\n    if (char == \"~\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexEditDistance\n    }\n\n    if (char == \"^\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexBoost\n    }\n\n    // \"+\" indicates term presence is required\n    // checking for length to ensure that only\n    // leading \"+\" are considered\n    if (char == \"+\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    // \"-\" indicates term presence is prohibited\n    // checking for length to ensure that only\n    // leading \"-\" are considered\n    if (char == \"-\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    if (char.match(lunr.QueryLexer.termSeparator)) {\n      return lunr.QueryLexer.lexTerm\n    }\n  }\n}\n\nlunr.QueryParser = function (str, query) {\n  this.lexer = new lunr.QueryLexer (str)\n  this.query = query\n  this.currentClause = {}\n  this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n  this.lexer.run()\n  this.lexemes = this.lexer.lexemes\n\n  var state = lunr.QueryParser.parseClause\n\n  while (state) {\n    state = state(this)\n  }\n\n  return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n  return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n  var lexeme = this.peekLexeme()\n  this.lexemeIdx += 1\n  return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n  var completedClause = this.currentClause\n  this.query.clause(completedClause)\n  this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n  var lexeme = parser.peekLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.type) {\n    case lunr.QueryLexer.PRESENCE:\n      return lunr.QueryParser.parsePresence\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n      if (lexeme.str.length >= 1) {\n        errorMessage += \" with value '\" + lexeme.str + \"'\"\n      }\n\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.str) {\n    case \"-\":\n      parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n      break\n    case \"+\":\n      parser.currentClause.presence = lunr.Query.presence.REQUIRED\n      break\n    default:\n      var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term or field, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n    var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n        errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.fields = [lexeme.str]\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  parser.currentClause.term = lexeme.str.toLowerCase()\n\n  if (lexeme.str.indexOf(\"*\") != -1) {\n    parser.currentClause.usePipeline = false\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var editDistance = parseInt(lexeme.str, 10)\n\n  if (isNaN(editDistance)) {\n    var errorMessage = \"edit distance must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.editDistance = editDistance\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var boost = parseInt(lexeme.str, 10)\n\n  if (isNaN(boost)) {\n    var errorMessage = \"boost must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.boost = boost\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\n  /**\n   * export the module via AMD, CommonJS or as a browser global\n   * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n   */\n  ;(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n      // AMD. Register as an anonymous module.\n      define(factory)\n    } else if (typeof exports === 'object') {\n      /**\n       * Node. Does not work with strict CommonJS, but\n       * only CommonJS-like enviroments that support module.exports,\n       * like Node.\n       */\n      module.exports = factory()\n    } else {\n      // Browser globals (root is window)\n      root.lunr = factory()\n    }\n  }(this, function () {\n    /**\n     * Just return a value to define the module export.\n     * This example returns an object, but the module\n     * can return a function as the exported value.\n     */\n    return lunr\n  }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n  function importScripts(...urls: string[]): Promise<void> | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n  config: SearchIndexConfig\n): Promise<void> {\n  let base = \"../lunr\"\n\n  /* Detect `iframe-worker` and fix base URL */\n  if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n    const worker = document.querySelector<HTMLScriptElement>(\"script[src]\")!\n    const [path] = worker.src.split(\"/worker\")\n\n    /* Prefix base with path */\n    base = base.replace(\"..\", path)\n  }\n\n  /* Add scripts for languages */\n  const scripts = []\n  for (const lang of config.lang) {\n    if (lang === \"ja\") scripts.push(`${base}/tinyseg.js`)\n    if (lang !== \"en\") scripts.push(`${base}/min/lunr.${lang}.min.js`)\n  }\n\n  /* Add multi-language support */\n  if (config.lang.length > 1)\n    scripts.push(`${base}/min/lunr.multi.min.js`)\n\n  /* Load scripts synchronously */\n  if (scripts.length)\n    await importScripts(\n      `${base}/min/lunr.stemmer.support.min.js`,\n      ...scripts\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n  message: SearchMessage\n): Promise<SearchMessage> {\n  switch (message.type) {\n\n    /* Search setup message */\n    case SearchMessageType.SETUP:\n      await setupSearchLanguages(message.data.config)\n      index = new Search(message.data)\n      return {\n        type: SearchMessageType.READY\n      }\n\n    /* Search query message */\n    case SearchMessageType.QUERY:\n      return {\n        type: SearchMessageType.RESULT,\n        data: index ? index.search(message.data) : []\n      }\n\n    /* All other messages */\n    default:\n      throw new TypeError(\"Invalid message type\")\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-ignore - expose Lunr.js in global scope, or stemmers will not work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n  postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n  config: SearchIndexConfig\n): SearchHighlightFactoryFn {\n  const separator = new RegExp(config.separator, \"img\")\n  const highlight = (_: unknown, data: string, term: string) => {\n    return `${data}<mark data-md-highlight>${term}</mark>`\n  }\n\n  /* Return factory function */\n  return (query: string) => {\n    query = query\n      .replace(/[\\s*+\\-:~^]+/g, \" \")\n      .trim()\n\n    /* Create search term match expression */\n    const match = new RegExp(`(^|${config.separator})(${\n      query\n        .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n        .replace(separator, \"|\")\n    })`, \"img\")\n\n    /* Highlight string value */\n    return value => value\n      .replace(match, highlight)\n      .replace(/<\\/mark>(\\s+)<mark[^>]*>/img, \"$1\")\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n  presence: lunr.Query.presence        /* Clause presence */\n  term: string                         /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record<string, boolean>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n  value: string\n): SearchQueryClause[] {\n  const query  = new (lunr as any).Query([\"title\", \"text\"])\n  const parser = new (lunr as any).QueryParser(value, query)\n\n  /* Parse and return query clauses */\n  parser.parse()\n  return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n  query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n  const clauses = new Set<SearchQueryClause>(query)\n\n  /* Match query clauses against terms */\n  const result: SearchQueryTerms = {}\n  for (let t = 0; t < terms.length; t++)\n    for (const clause of clauses)\n      if (terms[t].startsWith(clause.term)) {\n        result[clause.term] = true\n        clauses.delete(clause)\n      }\n\n  /* Annotate unmatched query clauses */\n  for (const clause of clauses)\n    result[clause.term] = false\n\n  /* Return query terms */\n  return result\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  SearchDocument,\n  SearchDocumentMap,\n  setupSearchDocumentMap\n} from \"../document\"\nimport {\n  SearchHighlightFactoryFn,\n  setupSearchHighlighter\n} from \"../highlighter\"\nimport {\n  SearchQueryTerms,\n  getSearchQueryTerms,\n  parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n  lang: string[]                       /* Search languages */\n  separator: string                    /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n  location: string                     /* Document location */\n  title: string                        /* Document title */\n  text: string                         /* Document text */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index pipeline function\n */\nexport type SearchIndexPipelineFn =\n  | \"trimmer\"                          /* Trimmer */\n  | \"stopWordFilter\"                   /* Stop word filter */\n  | \"stemmer\"                          /* Stemmer */\n\n/**\n * Search index pipeline\n */\nexport type SearchIndexPipeline = SearchIndexPipelineFn[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n  config: SearchIndexConfig            /* Search index configuration */\n  docs: SearchIndexDocument[]          /* Search index documents */\n  index?: object                       /* Prebuilt index */\n  pipeline?: SearchIndexPipeline       /* Search index pipeline */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n  score: number                        /* Score (relevance) */\n  terms: SearchQueryTerms              /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport type SearchResult = Array<SearchDocument & SearchMetadata>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n  const [x, y] = [new Set(a), new Set(b)]\n  return [\n    ...new Set([...x].filter(value => !y.has(value)))\n  ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n  /**\n   * Search document mapping\n   *\n   * A mapping of URLs (including hash fragments) to the actual articles and\n   * sections of the documentation. The search document mapping must be created\n   * regardless of whether the index was prebuilt or not, as Lunr.js itself\n   * only stores the actual index.\n   */\n  protected documents: SearchDocumentMap\n\n  /**\n   * Search highlight factory function\n   */\n  protected highlight: SearchHighlightFactoryFn\n\n  /**\n   * The underlying Lunr.js search index\n   */\n  protected index: lunr.Index\n\n  /**\n   * Create the search integration\n   *\n   * @param data - Search index\n   */\n  public constructor({ config, docs, pipeline, index }: SearchIndex) {\n    this.documents = setupSearchDocumentMap(docs)\n    this.highlight = setupSearchHighlighter(config)\n\n    /* Set separator for tokenizer */\n    lunr.tokenizer.separator = new RegExp(config.separator)\n\n    /* If no index was given, create it */\n    if (typeof index === \"undefined\") {\n      this.index = lunr(function () {\n\n        /* Set up multi-language support */\n        if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n          this.use((lunr as any)[config.lang[0]])\n        } else if (config.lang.length > 1) {\n          this.use((lunr as any).multiLanguage(...config.lang))\n        }\n\n        /* Compute functions to be removed from the pipeline */\n        const fns = difference([\n          \"trimmer\", \"stopWordFilter\", \"stemmer\"\n        ], pipeline!)\n\n        /* Remove functions from the pipeline for registered languages */\n        for (const lang of config.lang.map(language => (\n          language === \"en\" ? lunr : (lunr as any)[language]\n        ))) {\n          for (const fn of fns) {\n            this.pipeline.remove(lang[fn])\n            this.searchPipeline.remove(lang[fn])\n          }\n        }\n\n        /* Set up fields and reference */\n        this.field(\"title\", { boost: 1000 })\n        this.field(\"text\")\n        this.ref(\"location\")\n\n        /* Index documents */\n        for (const doc of docs)\n          this.add(doc)\n      })\n\n    /* Handle prebuilt index */\n    } else {\n      this.index = lunr.Index.load(index)\n    }\n  }\n\n  /**\n   * Search for matching documents\n   *\n   * The search index which MkDocs provides is divided up into articles, which\n   * contain the whole content of the individual pages, and sections, which only\n   * contain the contents of the subsections obtained by breaking the individual\n   * pages up at `h1` ... `h6`. As there may be many sections on different pages\n   * with identical titles (for example within this very project, e.g. \"Usage\"\n   * or \"Installation\"), they need to be put into the context of the containing\n   * page. For this reason, section results are grouped within their respective\n   * articles which are the top-level results that are returned.\n   *\n   * @param query - Query value\n   *\n   * @returns Search results\n   */\n  public search(query: string): SearchResult[] {\n    if (query) {\n      try {\n        const highlight = this.highlight(query)\n\n        /* Parse query to extract clauses for analysis */\n        const clauses = parseSearchQuery(query)\n          .filter(clause => (\n            clause.presence !== lunr.Query.presence.PROHIBITED\n          ))\n\n        /* Perform search and post-process results */\n        const groups = this.index.search(`${query}*`)\n\n          /* Apply post-query boosts based on title and search query terms */\n          .reduce<SearchResult>((results, { ref, score, matchData }) => {\n            const document = this.documents.get(ref)\n            if (typeof document !== \"undefined\") {\n              const { location, title, text, parent } = document\n\n              /* Compute and analyze search query terms */\n              const terms = getSearchQueryTerms(\n                clauses,\n                Object.keys(matchData.metadata)\n              )\n\n              /* Highlight title and text and apply post-query boosts */\n              const boost = +!parent + +Object.values(terms).every(t => t)\n              results.push({\n                location,\n                title: highlight(title),\n                text: highlight(text),\n                score: score * (1 + boost),\n                terms\n              })\n            }\n            return results\n          }, [])\n\n          /* Sort search results again after applying boosts */\n          .sort((a, b) => b.score - a.score)\n\n          /* Group search results by page */\n          .reduce((results, result) => {\n            const document = this.documents.get(result.location)\n            if (typeof document !== \"undefined\") {\n              const ref = \"parent\" in document\n                ? document.parent!.location\n                : document.location\n              results.set(ref, [...results.get(ref) || [], result])\n            }\n            return results\n          }, new Map<string, SearchResult>())\n\n        /* Expand grouped search results */\n        return [...groups.values()]\n\n      /* Log errors to console (for now) */\n      } catch {\n        console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n      }\n    }\n\n    /* Return nothing in case of error or empty query */\n    return []\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n"],
-  "mappings": "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AAG,GACV,EAAK,EACD,EAAG,KAAK,IACV,GAAI,WAEG,EAAI,KAAK,GAAI,CACtB,GAAI,GAAK,EAAI,KAAK,GAClB,EAAO,EAAG,GAAK,EAAG,GAClB,EAAM,EACF,EAAI,KAAK,IACX,GAAI,GAMR,GADA,EAAK,EACD,EAAG,KAAK,GAAI,CACd,GAAI,GAAK,EAAG,KAAK,GACjB,EAAO,EAAG,GACV,EAAK,EACL,EAAM,EACN,EAAM,EACF,GAAG,KAAK,IAAU,EAAI,KAAK,IAAS,CAAE,EAAI,KAAK,KACjD,GAAI,GAIR,SAAK,EACL,EAAM,EACF,EAAG,KAAK,IAAM,EAAI,KAAK,IACzB,GAAK,EACL,EAAI,EAAE,QAAQ,EAAG,KAKf,GAAW,KACb,GAAI,EAAQ,cAAgB,EAAE,OAAO,IAGhC,GAGT,MAAO,UAAU,EAAO,CACtB,MAAO,GAAM,OAAO,OAIxB,EAAK,SAAS,iBAAiB,EAAK,QAAS,WAC7C,AAkBA,EAAK,uBAAyB,SAAU,EAAW,CACjD,GAAI,GAAQ,EAAU,OAAO,SAAU,EAAM,EAAU,CACrD,SAAK,GAAY,EACV,GACN,IAEH,MAAO,UAAU,EAAO,CACtB,GAAI,GAAS,EAAM,EAAM,cAAgB,EAAM,WAAY,MAAO,KAiBtE,EAAK,eAAiB,EAAK,uBAAuB,CAChD,IACA,OACA,QACA,SACA,QACA,MACA,SACA,OACA,KACA,QACA,KACA,MACA,MACA,MACA,KACA,KACA,KACA,UACA,OACA,MACA,KACA,MACA,SACA,QACA,OACA,MACA,KACA,OACA,SACA,OACA,OACA,QACA,MACA,OACA,MACA,MACA,MACA,MACA,OACA,KACA,MACA,OACA,MACA,MACA,MACA,UACA,IACA,KACA,KACA,OACA,KACA,KACA,MACA,OACA,QACA,MACA,OACA,SACA,MACA,KACA,QACA,OACA,OACA,KACA,UACA,KACA,MACA,MACA,KACA,MACA,QACA,KACA,OACA,KACA,QACA,MACA,MACA,SACA,OACA,MACA,OACA,MACA,SACA,QACA,KACA,OACA,OACA,OACA,MACA,QACA,OACA,OACA,QACA,QACA,OACA,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",
-  "names": []
-}
diff --git a/5.4/assets/javascripts/workers/search.fe42c31b.min.js b/5.4/assets/javascripts/workers/search.fe42c31b.min.js
new file mode 100644 (file)
index 0000000..65bea7c
--- /dev/null
@@ -0,0 +1,61 @@
+(()=>{var le=Object.create,U=Object.defineProperty,he=Object.getPrototypeOf,de=Object.prototype.hasOwnProperty,fe=Object.getOwnPropertyNames,pe=Object.getOwnPropertyDescriptor;var ge=t=>U(t,"__esModule",{value:!0});var q=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var ye=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of fe(e))!de.call(t,n)&&n!=="default"&&U(t,n,{get:()=>e[n],enumerable:!(r=pe(e,n))||r.enumerable});return t},Y=t=>ye(ge(U(t!=null?le(he(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var X=q((G,J)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i<n.length;i++){var s=n[i],o=e[s];if(Array.isArray(o)){r[s]=o.slice();continue}if(typeof o=="string"||typeof o=="number"||typeof o=="boolean"){r[s]=o;continue}throw new TypeError("clone is not deep and does not support nested objects")}return r},t.FieldRef=function(e,r,n){this.docRef=e,this.fieldName=r,this._stringValue=n},t.FieldRef.joiner="/",t.FieldRef.fromString=function(e){var r=e.indexOf(t.FieldRef.joiner);if(r===-1)throw"malformed field ref string";var n=e.slice(0,r),i=e.slice(r+1);return new t.FieldRef(i,n,e)},t.FieldRef.prototype.toString=function(){return this._stringValue==null&&(this._stringValue=this.fieldName+t.FieldRef.joiner+this.docRef),this._stringValue};t.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var r=0;r<this.length;r++)this.elements[e[r]]=!0}else this.length=0},t.Set.complete={intersect:function(e){return e},union:function(){return this},contains:function(){return!0}},t.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},t.Set.prototype.contains=function(e){return!!this.elements[e]},t.Set.prototype.intersect=function(e){var r,n,i,s=[];if(e===t.Set.complete)return this;if(e===t.Set.empty)return e;this.length<e.length?(r=this,n=e):(r=e,n=this),i=Object.keys(r.elements);for(var o=0;o<i.length;o++){var a=i[o];a in n.elements&&s.push(a)}return new t.Set(s)},t.Set.prototype.union=function(e){return e===t.Set.complete?t.Set.complete:e===t.Set.empty?this:new t.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},t.idf=function(e,r){var n=0;for(var i in e)i!="_index"&&(n+=Object.keys(e[i]).length);var s=(r-n+.5)/(n+.5);return Math.log(1+Math.abs(s))},t.Token=function(e,r){this.str=e||"",this.metadata=r||{}},t.Token.prototype.toString=function(){return this.str},t.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},t.Token.prototype.clone=function(e){return e=e||function(r){return r},new t.Token(e(this.str,this.metadata),this.metadata)};t.tokenizer=function(e,r){if(e==null||e==null)return[];if(Array.isArray(e))return e.map(function(y){return new t.Token(t.utils.asString(y).toLowerCase(),t.utils.clone(r))});for(var n=e.toString().toLowerCase(),i=n.length,s=[],o=0,a=0;o<=i;o++){var u=n.charAt(o),c=o-a;if(u.match(t.tokenizer.separator)||o==i){if(c>0){var d=t.utils.clone(r)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index.
+`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n<r;n++){for(var i=this._stack[n],s=[],o=0;o<e.length;o++){var a=i(e[o],o,e);if(!(a==null||a===""))if(Array.isArray(a))for(var u=0;u<a.length;u++)s.push(a[u]);else s.push(a)}e=s}return e},t.Pipeline.prototype.runString=function(e,r){var n=new t.Token(e,r);return this.run([n]).map(function(i){return i.toString()})},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})};t.Vector=function(e){this._magnitude=0,this.elements=e||[]},t.Vector.prototype.positionForIndex=function(e){if(this.elements.length==0)return 0;for(var r=0,n=this.elements.length/2,i=n-r,s=Math.floor(i/2),o=this.elements[s*2];i>1&&(o<e&&(r=s),o>e&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(o<e)return(s+1)*2},t.Vector.prototype.insert=function(e,r){this.upsert(e,r,function(){throw"duplicate index"})},t.Vector.prototype.upsert=function(e,r,n){this._magnitude=0;var i=this.positionForIndex(e);this.elements[i]==e?this.elements[i+1]=n(this.elements[i+1],r):this.elements.splice(i,0,e,r)},t.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,r=this.elements.length,n=1;n<r;n+=2){var i=this.elements[n];e+=i*i}return this._magnitude=Math.sqrt(e)},t.Vector.prototype.dot=function(e){for(var r=0,n=this.elements,i=e.elements,s=n.length,o=i.length,a=0,u=0,c=0,d=0;c<s&&d<o;)a=n[c],u=i[d],a<u?c+=2:a>u?d+=2:a==u&&(r+=n[c+1]*i[d+1],c+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r<this.elements.length;r+=2,n++)e[n]=this.elements[r];return e},t.Vector.prototype.toJSON=function(){return this.elements};t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},r={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",s=n+"[^aeiouy]*",o=i+"[aeiou]*",a="^("+s+")?"+o+s,u="^("+s+")?"+o+s+"("+o+")?$",c="^("+s+")?"+o+s+o+s,d="^("+s+")?"+i,y=new RegExp(a),p=new RegExp(c),b=new RegExp(u),m=new RegExp(d),Q=/^(.+?)(ss|i)es$/,f=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,L=/^(.+?)(ed|ing)$/,w=/.$/,k=/(at|bl|iz)$/,O=new RegExp("([^aeiouylsz])\\1$"),j=new RegExp("^"+s+i+"[^aeiouwxy]$"),C=/^(.+?[^aeiou])y$/,A=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,V=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,$=/^(.+?)(s|t)(ion)$/,P=/^(.+?)e$/,N=/ll$/,B=new RegExp("^"+s+i+"[^aeiouwxy]$"),M=function(l){var v,I,E,h,x,T,F;if(l.length<3)return l;if(E=l.substr(0,1),E=="y"&&(l=E.toUpperCase()+l.substr(1)),h=Q,x=f,h.test(l)?l=l.replace(h,"$1$2"):x.test(l)&&(l=l.replace(x,"$1$2")),h=g,x=L,h.test(l)){var S=h.exec(l);h=y,h.test(S[1])&&(h=w,l=l.replace(h,""))}else if(x.test(l)){var S=x.exec(l);v=S[1],x=m,x.test(v)&&(l=v,x=k,T=O,F=j,x.test(l)?l=l+"e":T.test(l)?(h=w,l=l.replace(h,"")):F.test(l)&&(l=l+"e"))}if(h=C,h.test(l)){var S=h.exec(l);v=S[1],l=v+"i"}if(h=A,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+e[I])}if(h=V,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+r[I])}if(h=D,x=$,h.test(l)){var S=h.exec(l);v=S[1],h=p,h.test(v)&&(l=v)}else if(x.test(l)){var S=x.exec(l);v=S[1]+S[2],x=p,x.test(v)&&(l=v)}if(h=P,h.test(l)){var S=h.exec(l);v=S[1],h=p,x=b,T=B,(h.test(v)||x.test(v)&&!T.test(v))&&(l=v)}return h=N,x=p,h.test(l)&&x.test(l)&&(h=w,l=l.replace(h,"")),E=="y"&&(l=E.toLowerCase()+l.substr(1)),l};return function(_){return _.update(M)}}(),t.Pipeline.registerFunction(t.stemmer,"stemmer");t.generateStopWordFilter=function(e){var r=e.reduce(function(n,i){return n[i]=i,n},{});return function(n){if(n&&r[n.toString()]!==n.toString())return n}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter");t.trimmer=function(e){return e.update(function(r){return r.replace(/^\W+/,"").replace(/\W+$/,"")})},t.Pipeline.registerFunction(t.trimmer,"trimmer");t.TokenSet=function(){this.final=!1,this.edges={},this.id=t.TokenSet._nextId,t.TokenSet._nextId+=1},t.TokenSet._nextId=1,t.TokenSet.fromArray=function(e){for(var r=new t.TokenSet.Builder,n=0,i=e.length;n<i;n++)r.insert(e[n]);return r.finish(),r.root},t.TokenSet.fromClause=function(e){return"editDistance"in e?t.TokenSet.fromFuzzyString(e.term,e.editDistance):t.TokenSet.fromString(e.term)},t.TokenSet.fromFuzzyString=function(e,r){for(var n=new t.TokenSet,i=[{node:n,editsRemaining:r,str:e}];i.length;){var s=i.pop();if(s.str.length>0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),y=s.str.charAt(1),p;y in s.node.edges?p=s.node.edges[y]:(p=new t.TokenSet,s.node.edges[y]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i<s;i++){var o=e[i],a=i==s-1;if(o=="*")r.edges[o]=r,r.final=a;else{var u=new t.TokenSet;u.final=a,r.edges[o]=u,r=u}}return n},t.TokenSet.prototype.toArray=function(){for(var e=[],r=[{prefix:"",node:this}];r.length;){var n=r.pop(),i=Object.keys(n.node.edges),s=i.length;n.node.final&&(n.prefix.charAt(0),e.push(n.prefix));for(var o=0;o<s;o++){var a=i[o];r.push({prefix:n.prefix.concat(a),node:n.node.edges[a]})}}return e},t.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",r=Object.keys(this.edges).sort(),n=r.length,i=0;i<n;i++){var s=r[i],o=this.edges[s];e=e+s+o.id}return e},t.TokenSet.prototype.intersect=function(e){for(var r=new t.TokenSet,n=void 0,i=[{qNode:e,output:r,node:this}];i.length;){n=i.pop();for(var s=Object.keys(n.qNode.edges),o=s.length,a=Object.keys(n.node.edges),u=a.length,c=0;c<o;c++)for(var d=s[c],y=0;y<u;y++){var p=a[y];if(p==d||d=="*"){var b=n.node.edges[p],m=n.qNode.edges[d],Q=b.final&&m.final,f=void 0;p in n.output.edges?(f=n.output.edges[p],f.final=f.final||Q):(f=new t.TokenSet,f.final=Q,n.output.edges[p]=f),i.push({qNode:m,output:f,node:b})}}}return r},t.TokenSet.Builder=function(){this.previousWord="",this.root=new t.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},t.TokenSet.Builder.prototype.insert=function(e){var r,n=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var i=0;i<e.length&&i<this.previousWord.length&&e[i]==this.previousWord[i];i++)n++;this.minimize(n),this.uncheckedNodes.length==0?r=this.root:r=this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(var i=n;i<e.length;i++){var s=new t.TokenSet,o=e[i];r.edges[o]=s,this.uncheckedNodes.push({parent:r,char:o,child:s}),r=s}r.final=!0,this.previousWord=e},t.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},t.TokenSet.Builder.prototype.minimize=function(e){for(var r=this.uncheckedNodes.length-1;r>=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u<this.fields.length;u++)i[this.fields[u]]=new t.Vector;e.call(r,r);for(var u=0;u<r.clauses.length;u++){var c=r.clauses[u],d=null,y=t.Set.empty;c.usePipeline?d=this.pipeline.runString(c.term,{fields:c.fields}):d=[c.term];for(var p=0;p<d.length;p++){var b=d[p];c.term=b;var m=t.TokenSet.fromClause(c),Q=this.tokenSet.intersect(m).toArray();if(Q.length===0&&c.presence===t.Query.presence.REQUIRED){for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=t.Set.empty}break}for(var L=0;L<Q.length;L++)for(var w=Q[L],k=this.invertedIndex[w],O=k._index,f=0;f<c.fields.length;f++){var g=c.fields[f],j=k[g],C=Object.keys(j),A=w+"/"+g,V=new t.Set(C);if(c.presence==t.Query.presence.REQUIRED&&(y=y.union(V),o[g]===void 0&&(o[g]=t.Set.complete)),c.presence==t.Query.presence.PROHIBITED){a[g]===void 0&&(a[g]=t.Set.empty),a[g]=a[g].union(V);continue}if(i[g].upsert(O,c.boost,function(ue,ce){return ue+ce}),!s[A]){for(var D=0;D<C.length;D++){var $=C[D],P=new t.FieldRef($,g),N=j[$],B;(B=n[P])===void 0?n[P]=new t.MatchData(w,g,N):B.add(w,g,N)}s[A]=!0}}}if(c.presence===t.Query.presence.REQUIRED)for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=o[g].intersect(y)}}for(var M=t.Set.complete,_=t.Set.empty,u=0;u<this.fields.length;u++){var g=this.fields[u];o[g]&&(M=M.intersect(o[g])),a[g]&&(_=_.union(a[g]))}var l=Object.keys(n),v=[],I=Object.create(null);if(r.isNegated()){l=Object.keys(this.fieldVectors);for(var u=0;u<l.length;u++){var P=l[u],E=t.FieldRef.fromString(P);n[P]=new t.MatchData}}for(var u=0;u<l.length;u++){var E=t.FieldRef.fromString(l[u]),h=E.docRef;if(!!M.contains(h)&&!_.contains(h)){var x=this.fieldVectors[E],T=i[E.fieldName].similarity(x),F;if((F=I[h])!==void 0)F.score+=T,F.matchData.combine(n[E]);else{var S={ref:h,score:T,matchData:n[E]};I[h]=S,v.push(S)}}}return v.sort(function(oe,ae){return ae.score-oe.score})},t.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map(function(n){return[n,this.invertedIndex[n]]},this),r=Object.keys(this.fieldVectors).map(function(n){return[n,this.fieldVectors[n].toJSON()]},this);return{version:t.version,fields:this.fields,fieldVectors:r,invertedIndex:e,pipeline:this.pipeline.toJSON()}},t.Index.load=function(e){var r={},n={},i=e.fieldVectors,s=Object.create(null),o=e.invertedIndex,a=new t.TokenSet.Builder,u=t.Pipeline.load(e.pipeline);e.version!=t.version&&t.utils.warn("Version mismatch when loading serialised index. Current version of lunr '"+t.version+"' does not match serialized index '"+e.version+"'");for(var c=0;c<i.length;c++){var d=i[c],y=d[0],p=d[1];n[y]=new t.Vector(p)}for(var c=0;c<o.length;c++){var d=o[c],b=d[0],m=d[1];a.insert(b),s[b]=m}return a.finish(),r.fields=e.fields,r.fieldVectors=n,r.invertedIndex=s,r.tokenSet=a.root,r.pipeline=u,new t.Index(r)};t.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=t.tokenizer,this.pipeline=new t.Pipeline,this.searchPipeline=new t.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},t.Builder.prototype.ref=function(e){this._ref=e},t.Builder.prototype.field=function(e,r){if(/\//.test(e))throw new RangeError("Field '"+e+"' contains illegal character '/'");this._fields[e]=r||{}},t.Builder.prototype.b=function(e){e<0?this._b=0:e>1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s<i.length;s++){var o=i[s],a=this._fields[o].extractor,u=a?a(e):e[o],c=this.tokenizer(u,{fields:[o]}),d=this.pipeline.run(c),y=new t.FieldRef(n,o),p=Object.create(null);this.fieldTermFrequencies[y]=p,this.fieldLengths[y]=0,this.fieldLengths[y]+=d.length;for(var b=0;b<d.length;b++){var m=d[b];if(p[m]==null&&(p[m]=0),p[m]+=1,this.invertedIndex[m]==null){var Q=Object.create(null);Q._index=this.termIndex,this.termIndex+=1;for(var f=0;f<i.length;f++)Q[i[f]]=Object.create(null);this.invertedIndex[m]=Q}this.invertedIndex[m][o][n]==null&&(this.invertedIndex[m][o][n]=Object.create(null));for(var g=0;g<this.metadataWhitelist.length;g++){var L=this.metadataWhitelist[g],w=m.metadata[L];this.invertedIndex[m][o][n][L]==null&&(this.invertedIndex[m][o][n][L]=[]),this.invertedIndex[m][o][n][L].push(w)}}}},t.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),r=e.length,n={},i={},s=0;s<r;s++){var o=t.FieldRef.fromString(e[s]),a=o.fieldName;i[a]||(i[a]=0),i[a]+=1,n[a]||(n[a]=0),n[a]+=this.fieldLengths[o]}for(var u=Object.keys(this._fields),s=0;s<u.length;s++){var c=u[s];n[c]=n[c]/i[c]}this.averageFieldLength=n},t.Builder.prototype.createFieldVectors=function(){for(var e={},r=Object.keys(this.fieldTermFrequencies),n=r.length,i=Object.create(null),s=0;s<n;s++){for(var o=t.FieldRef.fromString(r[s]),a=o.fieldName,u=this.fieldLengths[o],c=new t.Vector,d=this.fieldTermFrequencies[o],y=Object.keys(d),p=y.length,b=this._fields[a].boost||1,m=this._documents[o.docRef].boost||1,Q=0;Q<p;Q++){var f=y[Q],g=d[f],L=this.invertedIndex[f]._index,w,k,O;i[f]===void 0?(w=t.idf(this.invertedIndex[f],this.documentCount),i[f]=w):w=i[f],k=w*((this._k1+1)*g)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[a]))+g),k*=b,k*=m,O=Math.round(k*1e3)/1e3,c.insert(L,O)}e[o]=c}this.fieldVectors=e},t.Builder.prototype.createTokenSet=function(){this.tokenSet=t.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},t.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new t.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},t.Builder.prototype.use=function(e){var r=Array.prototype.slice.call(arguments,1);r.unshift(this),e.apply(this,r)},t.MatchData=function(e,r,n){for(var i=Object.create(null),s=Object.keys(n||{}),o=0;o<s.length;o++){var a=s[o];i[a]=n[a].slice()}this.metadata=Object.create(null),e!==void 0&&(this.metadata[e]=Object.create(null),this.metadata[e][r]=i)},t.MatchData.prototype.combine=function(e){for(var r=Object.keys(e.metadata),n=0;n<r.length;n++){var i=r[n],s=Object.keys(e.metadata[i]);this.metadata[i]==null&&(this.metadata[i]=Object.create(null));for(var o=0;o<s.length;o++){var a=s[o],u=Object.keys(e.metadata[i][a]);this.metadata[i][a]==null&&(this.metadata[i][a]=Object.create(null));for(var c=0;c<u.length;c++){var d=u[c];this.metadata[i][a][d]==null?this.metadata[i][a][d]=e.metadata[i][a][d]:this.metadata[i][a][d]=this.metadata[i][a][d].concat(e.metadata[i][a][d])}}}},t.MatchData.prototype.add=function(e,r,n){if(!(e in this.metadata)){this.metadata[e]=Object.create(null),this.metadata[e][r]=n;return}if(!(r in this.metadata[e])){this.metadata[e][r]=n;return}for(var i=Object.keys(n),s=0;s<i.length;s++){var o=i[s];o in this.metadata[e][r]?this.metadata[e][r][o]=this.metadata[e][r][o].concat(n[o]):this.metadata[e][r][o]=n[o]}},t.Query=function(e){this.clauses=[],this.allFields=e},t.Query.wildcard=new String("*"),t.Query.wildcard.NONE=0,t.Query.wildcard.LEADING=1,t.Query.wildcard.TRAILING=2,t.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},t.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=t.Query.wildcard.NONE),e.wildcard&t.Query.wildcard.LEADING&&e.term.charAt(0)!=t.Query.wildcard&&(e.term="*"+e.term),e.wildcard&t.Query.wildcard.TRAILING&&e.term.slice(-1)!=t.Query.wildcard&&(e.term=""+e.term+"*"),"presence"in e||(e.presence=t.Query.presence.OPTIONAL),this.clauses.push(e),this},t.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=t.Query.presence.PROHIBITED)return!1;return!0},t.Query.prototype.term=function(e,r){if(Array.isArray(e))return e.forEach(function(i){this.term(i,t.utils.clone(r))},this),this;var n=r||{};return n.term=e.toString(),this.clause(n),this},t.QueryParseError=function(e,r,n){this.name="QueryParseError",this.message=e,this.start=r,this.end=n},t.QueryParseError.prototype=new Error,t.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},t.QueryLexer.prototype.run=function(){for(var e=t.QueryLexer.lexText;e;)e=e(this)},t.QueryLexer.prototype.sliceString=function(){for(var e=[],r=this.start,n=this.pos,i=0;i<this.escapeCharPositions.length;i++)n=this.escapeCharPositions[i],e.push(this.str.slice(r,n)),r=n+1;return e.push(this.str.slice(r,this.pos)),this.escapeCharPositions.length=0,e.join("")},t.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},t.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},t.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos<this.length},t.QueryLexer.EOS="EOS",t.QueryLexer.FIELD="FIELD",t.QueryLexer.TERM="TERM",t.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",t.QueryLexer.BOOST="BOOST",t.QueryLexer.PRESENCE="PRESENCE",t.QueryLexer.lexField=function(e){return e.backup(),e.emit(t.QueryLexer.FIELD),e.ignore(),t.QueryLexer.lexText},t.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof G=="object"?J.exports=r():e.lunr=r()}(this,function(){return t})})()});var K=q((we,Z)=>{"use strict";var me=/["'&<>]/;Z.exports=ve;function ve(t){var e=""+t,r=me.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s<e.length;s++){switch(e.charCodeAt(s)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}o!==s&&(i+=e.substring(o,s)),o=s+1,i+=n}return o!==s?i+e.substring(o,s):i}});var se=Y(X());var ee=Y(K());function te(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=(0,ee.default)(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let c=e.get(i);r.has(c)?e.set(o,{location:o,title:a,text:u,parent:c}):(c.title=n.title,c.text=u,r.add(c))}else e.set(o,{location:o,title:a,text:u})}return e}function re(t){let e=new RegExp(t.separator,"img"),r=(n,i,s)=>`${i}<mark data-md-highlight>${s}</mark>`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${t.separator})(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(e,"|")})`,"img");return s=>s.replace(i,r).replace(/<\/mark>(\s+)<mark[^>]*>/img,"$1")}}function ne(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ie(t,e){let r=new Set(t),n={};for(let i=0;i<e.length;i++)for(let s of r)e[i].startsWith(s.term)&&(n[s.term]=!0,r.delete(s));for(let i of r)n[i.term]=!1;return n}function xe(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var W=class{constructor({config:e,docs:r,pipeline:n,index:i}){this.documents=te(r),this.highlight=re(e),lunr.tokenizer.separator=new RegExp(e.separator),typeof i=="undefined"?this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let s=xe(["trimmer","stopWordFilter","stemmer"],n);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.field("title",{boost:1e3}),this.field("text"),this.ref("location");for(let o of r)this.add(o)}):this.index=lunr.Index.load(i)}search(e){if(e)try{let r=this.highlight(e),n=ne(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED);return[...this.index.search(`${e}*`).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.documents.get(o);if(typeof c!="undefined"){let{location:d,title:y,text:p,parent:b}=c,m=ie(n,Object.keys(u.metadata)),Q=+!b+ +Object.values(m).every(f=>f);s.push({location:d,title:r(y),text:r(p),score:a*(1+Q),terms:m})}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.documents.get(o.location);if(typeof a!="undefined"){let u="parent"in a?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map).values()]}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return[]}};var R;(function(t){t[t.SETUP=0]="SETUP",t[t.READY=1]="READY",t[t.QUERY=2]="QUERY",t[t.RESULT=3]="RESULT"})(R||(R={}));var H;function Se(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang)n==="ja"&&r.push(`${e}/tinyseg.js`),n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`);t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Qe(t){return z(this,null,function*(){switch(t.type){case R.SETUP:return yield Se(t.data.config),H=new W(t.data),{type:R.READY};case R.QUERY:return{type:R.RESULT,data:H?H.search(t.data):[]};default:throw new TypeError("Invalid message type")}})}self.lunr=se.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Qe(t.data))}));})();
+/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */
+/*!
+ * lunr.Builder
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Index
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Pipeline
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Set
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.TokenSet
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Vector
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.stemmer
+ * Copyright (C) 2020 Oliver Nightingale
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
+ */
+/*!
+ * lunr.stopWordFilter
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.tokenizer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.trimmer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.utils
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/**
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
+ * Copyright (C) 2020 Oliver Nightingale
+ * @license MIT
+ */
+//# sourceMappingURL=search.fe42c31b.min.js.map
+
diff --git a/5.4/assets/javascripts/workers/search.fe42c31b.min.js.map b/5.4/assets/javascripts/workers/search.fe42c31b.min.js.map
new file mode 100644 (file)
index 0000000..3c9fcb6
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "version": 3,
+  "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts"],
+  "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n *   this.field('title')\n *   this.field('body')\n *   this.ref('id')\n *\n *   documents.forEach(function (doc) {\n *     this.add(doc)\n *   }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n  var builder = new lunr.Builder\n\n  builder.pipeline.add(\n    lunr.trimmer,\n    lunr.stopWordFilter,\n    lunr.stemmer\n  )\n\n  builder.searchPipeline.add(\n    lunr.stemmer\n  )\n\n  config.call(builder, builder)\n  return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n  /* eslint-disable no-console */\n  return function (message) {\n    if (global.console && console.warn) {\n      console.warn(message)\n    }\n  }\n  /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n  if (obj === void 0 || obj === null) {\n    return \"\"\n  } else {\n    return obj.toString()\n  }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n  if (obj === null || obj === undefined) {\n    return obj\n  }\n\n  var clone = Object.create(null),\n      keys = Object.keys(obj)\n\n  for (var i = 0; i < keys.length; i++) {\n    var key = keys[i],\n        val = obj[key]\n\n    if (Array.isArray(val)) {\n      clone[key] = val.slice()\n      continue\n    }\n\n    if (typeof val === 'string' ||\n        typeof val === 'number' ||\n        typeof val === 'boolean') {\n      clone[key] = val\n      continue\n    }\n\n    throw new TypeError(\"clone is not deep and does not support nested objects\")\n  }\n\n  return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n  this.docRef = docRef\n  this.fieldName = fieldName\n  this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n  var n = s.indexOf(lunr.FieldRef.joiner)\n\n  if (n === -1) {\n    throw \"malformed field ref string\"\n  }\n\n  var fieldRef = s.slice(0, n),\n      docRef = s.slice(n + 1)\n\n  return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n  if (this._stringValue == undefined) {\n    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n  }\n\n  return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n  this.elements = Object.create(null)\n\n  if (elements) {\n    this.length = elements.length\n\n    for (var i = 0; i < this.length; i++) {\n      this.elements[elements[i]] = true\n    }\n  } else {\n    this.length = 0\n  }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n  intersect: function (other) {\n    return other\n  },\n\n  union: function () {\n    return this\n  },\n\n  contains: function () {\n    return true\n  }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n  intersect: function () {\n    return this\n  },\n\n  union: function (other) {\n    return other\n  },\n\n  contains: function () {\n    return false\n  }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n  return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n  var a, b, elements, intersection = []\n\n  if (other === lunr.Set.complete) {\n    return this\n  }\n\n  if (other === lunr.Set.empty) {\n    return other\n  }\n\n  if (this.length < other.length) {\n    a = this\n    b = other\n  } else {\n    a = other\n    b = this\n  }\n\n  elements = Object.keys(a.elements)\n\n  for (var i = 0; i < elements.length; i++) {\n    var element = elements[i]\n    if (element in b.elements) {\n      intersection.push(element)\n    }\n  }\n\n  return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n  if (other === lunr.Set.complete) {\n    return lunr.Set.complete\n  }\n\n  if (other === lunr.Set.empty) {\n    return this\n  }\n\n  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n  var documentsWithTerm = 0\n\n  for (var fieldName in posting) {\n    if (fieldName == '_index') continue // Ignore the term index, its not a field\n    documentsWithTerm += Object.keys(posting[fieldName]).length\n  }\n\n  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n  return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n  this.str = str || \"\"\n  this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n  return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n *   return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n  this.str = fn(this.str, this.metadata)\n  return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n  fn = fn || function (s) { return s }\n  return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n  if (obj == null || obj == undefined) {\n    return []\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.map(function (t) {\n      return new lunr.Token(\n        lunr.utils.asString(t).toLowerCase(),\n        lunr.utils.clone(metadata)\n      )\n    })\n  }\n\n  var str = obj.toString().toLowerCase(),\n      len = str.length,\n      tokens = []\n\n  for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n    var char = str.charAt(sliceEnd),\n        sliceLength = sliceEnd - sliceStart\n\n    if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n      if (sliceLength > 0) {\n        var tokenMetadata = lunr.utils.clone(metadata) || {}\n        tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n        tokenMetadata[\"index\"] = tokens.length\n\n        tokens.push(\n          new lunr.Token (\n            str.slice(sliceStart, sliceEnd),\n            tokenMetadata\n          )\n        )\n      }\n\n      sliceStart = sliceEnd + 1\n    }\n\n  }\n\n  return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n  this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n  if (label in this.registeredFunctions) {\n    lunr.utils.warn('Overwriting existing registered function: ' + label)\n  }\n\n  fn.label = label\n  lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n  var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n  if (!isRegistered) {\n    lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n  }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n  var pipeline = new lunr.Pipeline\n\n  serialised.forEach(function (fnName) {\n    var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n    if (fn) {\n      pipeline.add(fn)\n    } else {\n      throw new Error('Cannot load unregistered function: ' + fnName)\n    }\n  })\n\n  return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n  var fns = Array.prototype.slice.call(arguments)\n\n  fns.forEach(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n    this._stack.push(fn)\n  }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  pos = pos + 1\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n  var pos = this._stack.indexOf(fn)\n  if (pos == -1) {\n    return\n  }\n\n  this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n  var stackLength = this._stack.length\n\n  for (var i = 0; i < stackLength; i++) {\n    var fn = this._stack[i]\n    var memo = []\n\n    for (var j = 0; j < tokens.length; j++) {\n      var result = fn(tokens[j], j, tokens)\n\n      if (result === null || result === void 0 || result === '') continue\n\n      if (Array.isArray(result)) {\n        for (var k = 0; k < result.length; k++) {\n          memo.push(result[k])\n        }\n      } else {\n        memo.push(result)\n      }\n    }\n\n    tokens = memo\n  }\n\n  return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n  var token = new lunr.Token (str, metadata)\n\n  return this.run([token]).map(function (t) {\n    return t.toString()\n  })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n  this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n  return this._stack.map(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n    return fn.label\n  })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n  this._magnitude = 0\n  this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n  // For an empty vector the tuple can be inserted at the beginning\n  if (this.elements.length == 0) {\n    return 0\n  }\n\n  var start = 0,\n      end = this.elements.length / 2,\n      sliceLength = end - start,\n      pivotPoint = Math.floor(sliceLength / 2),\n      pivotIndex = this.elements[pivotPoint * 2]\n\n  while (sliceLength > 1) {\n    if (pivotIndex < index) {\n      start = pivotPoint\n    }\n\n    if (pivotIndex > index) {\n      end = pivotPoint\n    }\n\n    if (pivotIndex == index) {\n      break\n    }\n\n    sliceLength = end - start\n    pivotPoint = start + Math.floor(sliceLength / 2)\n    pivotIndex = this.elements[pivotPoint * 2]\n  }\n\n  if (pivotIndex == index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex > index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex < index) {\n    return (pivotPoint + 1) * 2\n  }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n  this.upsert(insertIdx, val, function () {\n    throw \"duplicate index\"\n  })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n  this._magnitude = 0\n  var position = this.positionForIndex(insertIdx)\n\n  if (this.elements[position] == insertIdx) {\n    this.elements[position + 1] = fn(this.elements[position + 1], val)\n  } else {\n    this.elements.splice(position, 0, insertIdx, val)\n  }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n  if (this._magnitude) return this._magnitude\n\n  var sumOfSquares = 0,\n      elementsLength = this.elements.length\n\n  for (var i = 1; i < elementsLength; i += 2) {\n    var val = this.elements[i]\n    sumOfSquares += val * val\n  }\n\n  return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n  var dotProduct = 0,\n      a = this.elements, b = otherVector.elements,\n      aLen = a.length, bLen = b.length,\n      aVal = 0, bVal = 0,\n      i = 0, j = 0\n\n  while (i < aLen && j < bLen) {\n    aVal = a[i], bVal = b[j]\n    if (aVal < bVal) {\n      i += 2\n    } else if (aVal > bVal) {\n      j += 2\n    } else if (aVal == bVal) {\n      dotProduct += a[i + 1] * b[j + 1]\n      i += 2\n      j += 2\n    }\n  }\n\n  return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n  return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n  var output = new Array (this.elements.length / 2)\n\n  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n    output[j] = this.elements[i]\n  }\n\n  return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n  return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n  var step2list = {\n      \"ational\" : \"ate\",\n      \"tional\" : \"tion\",\n      \"enci\" : \"ence\",\n      \"anci\" : \"ance\",\n      \"izer\" : \"ize\",\n      \"bli\" : \"ble\",\n      \"alli\" : \"al\",\n      \"entli\" : \"ent\",\n      \"eli\" : \"e\",\n      \"ousli\" : \"ous\",\n      \"ization\" : \"ize\",\n      \"ation\" : \"ate\",\n      \"ator\" : \"ate\",\n      \"alism\" : \"al\",\n      \"iveness\" : \"ive\",\n      \"fulness\" : \"ful\",\n      \"ousness\" : \"ous\",\n      \"aliti\" : \"al\",\n      \"iviti\" : \"ive\",\n      \"biliti\" : \"ble\",\n      \"logi\" : \"log\"\n    },\n\n    step3list = {\n      \"icate\" : \"ic\",\n      \"ative\" : \"\",\n      \"alize\" : \"al\",\n      \"iciti\" : \"ic\",\n      \"ical\" : \"ic\",\n      \"ful\" : \"\",\n      \"ness\" : \"\"\n    },\n\n    c = \"[^aeiou]\",          // consonant\n    v = \"[aeiouy]\",          // vowel\n    C = c + \"[^aeiouy]*\",    // consonant sequence\n    V = v + \"[aeiou]*\",      // vowel sequence\n\n    mgr0 = \"^(\" + C + \")?\" + V + C,               // [C]VC... is m>0\n    meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\",  // [C]VC[V] is m=1\n    mgr1 = \"^(\" + C + \")?\" + V + C + V + C,       // [C]VCVC... is m>1\n    s_v = \"^(\" + C + \")?\" + v;                   // vowel in stem\n\n  var re_mgr0 = new RegExp(mgr0);\n  var re_mgr1 = new RegExp(mgr1);\n  var re_meq1 = new RegExp(meq1);\n  var re_s_v = new RegExp(s_v);\n\n  var re_1a = /^(.+?)(ss|i)es$/;\n  var re2_1a = /^(.+?)([^s])s$/;\n  var re_1b = /^(.+?)eed$/;\n  var re2_1b = /^(.+?)(ed|ing)$/;\n  var re_1b_2 = /.$/;\n  var re2_1b_2 = /(at|bl|iz)$/;\n  var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n  var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var re_1c = /^(.+?[^aeiou])y$/;\n  var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n  var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n  var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n  var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n  var re_5 = /^(.+?)e$/;\n  var re_5_1 = /ll$/;\n  var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var porterStemmer = function porterStemmer(w) {\n    var stem,\n      suffix,\n      firstch,\n      re,\n      re2,\n      re3,\n      re4;\n\n    if (w.length < 3) { return w; }\n\n    firstch = w.substr(0,1);\n    if (firstch == \"y\") {\n      w = firstch.toUpperCase() + w.substr(1);\n    }\n\n    // Step 1a\n    re = re_1a\n    re2 = re2_1a;\n\n    if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n    else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n    // Step 1b\n    re = re_1b;\n    re2 = re2_1b;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      re = re_mgr0;\n      if (re.test(fp[1])) {\n        re = re_1b_2;\n        w = w.replace(re,\"\");\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1];\n      re2 = re_s_v;\n      if (re2.test(stem)) {\n        w = stem;\n        re2 = re2_1b_2;\n        re3 = re3_1b_2;\n        re4 = re4_1b_2;\n        if (re2.test(w)) { w = w + \"e\"; }\n        else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n        else if (re4.test(w)) { w = w + \"e\"; }\n      }\n    }\n\n    // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n    re = re_1c;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      w = stem + \"i\";\n    }\n\n    // Step 2\n    re = re_2;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step2list[suffix];\n      }\n    }\n\n    // Step 3\n    re = re_3;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step3list[suffix];\n      }\n    }\n\n    // Step 4\n    re = re_4;\n    re2 = re2_4;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      if (re.test(stem)) {\n        w = stem;\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1] + fp[2];\n      re2 = re_mgr1;\n      if (re2.test(stem)) {\n        w = stem;\n      }\n    }\n\n    // Step 5\n    re = re_5;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      re2 = re_meq1;\n      re3 = re3_5;\n      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n        w = stem;\n      }\n    }\n\n    re = re_5_1;\n    re2 = re_mgr1;\n    if (re.test(w) && re2.test(w)) {\n      re = re_1b_2;\n      w = w.replace(re,\"\");\n    }\n\n    // and turn initial Y back to y\n\n    if (firstch == \"y\") {\n      w = firstch.toLowerCase() + w.substr(1);\n    }\n\n    return w;\n  };\n\n  return function (token) {\n    return token.update(porterStemmer);\n  }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n  var words = stopWords.reduce(function (memo, stopWord) {\n    memo[stopWord] = stopWord\n    return memo\n  }, {})\n\n  return function (token) {\n    if (token && words[token.toString()] !== token.toString()) return token\n  }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n  'a',\n  'able',\n  'about',\n  'across',\n  'after',\n  'all',\n  'almost',\n  'also',\n  'am',\n  'among',\n  'an',\n  'and',\n  'any',\n  'are',\n  'as',\n  'at',\n  'be',\n  'because',\n  'been',\n  'but',\n  'by',\n  'can',\n  'cannot',\n  'could',\n  'dear',\n  'did',\n  'do',\n  'does',\n  'either',\n  'else',\n  'ever',\n  'every',\n  'for',\n  'from',\n  'get',\n  'got',\n  'had',\n  'has',\n  'have',\n  'he',\n  'her',\n  'hers',\n  'him',\n  'his',\n  'how',\n  'however',\n  'i',\n  'if',\n  'in',\n  'into',\n  'is',\n  'it',\n  'its',\n  'just',\n  'least',\n  'let',\n  'like',\n  'likely',\n  'may',\n  'me',\n  'might',\n  'most',\n  'must',\n  'my',\n  'neither',\n  'no',\n  'nor',\n  'not',\n  'of',\n  'off',\n  'often',\n  'on',\n  'only',\n  'or',\n  'other',\n  'our',\n  'own',\n  'rather',\n  'said',\n  'say',\n  'says',\n  'she',\n  'should',\n  'since',\n  'so',\n  'some',\n  'than',\n  'that',\n  'the',\n  'their',\n  'them',\n  'then',\n  'there',\n  'these',\n  'they',\n  'this',\n  'tis',\n  'to',\n  'too',\n  'twas',\n  'us',\n  'wants',\n  'was',\n  'we',\n  'were',\n  'what',\n  'when',\n  'where',\n  'which',\n  'while',\n  'who',\n  'whom',\n  'why',\n  'will',\n  'with',\n  'would',\n  'yet',\n  'you',\n  'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n  return token.update(function (s) {\n    return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n  })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n  this.final = false\n  this.edges = {}\n  this.id = lunr.TokenSet._nextId\n  lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n  var builder = new lunr.TokenSet.Builder\n\n  for (var i = 0, len = arr.length; i < len; i++) {\n    builder.insert(arr[i])\n  }\n\n  builder.finish()\n  return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n  if ('editDistance' in clause) {\n    return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n  } else {\n    return lunr.TokenSet.fromString(clause.term)\n  }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n  var root = new lunr.TokenSet\n\n  var stack = [{\n    node: root,\n    editsRemaining: editDistance,\n    str: str\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop()\n\n    // no edit\n    if (frame.str.length > 0) {\n      var char = frame.str.charAt(0),\n          noEditNode\n\n      if (char in frame.node.edges) {\n        noEditNode = frame.node.edges[char]\n      } else {\n        noEditNode = new lunr.TokenSet\n        frame.node.edges[char] = noEditNode\n      }\n\n      if (frame.str.length == 1) {\n        noEditNode.final = true\n      }\n\n      stack.push({\n        node: noEditNode,\n        editsRemaining: frame.editsRemaining,\n        str: frame.str.slice(1)\n      })\n    }\n\n    if (frame.editsRemaining == 0) {\n      continue\n    }\n\n    // insertion\n    if (\"*\" in frame.node.edges) {\n      var insertionNode = frame.node.edges[\"*\"]\n    } else {\n      var insertionNode = new lunr.TokenSet\n      frame.node.edges[\"*\"] = insertionNode\n    }\n\n    if (frame.str.length == 0) {\n      insertionNode.final = true\n    }\n\n    stack.push({\n      node: insertionNode,\n      editsRemaining: frame.editsRemaining - 1,\n      str: frame.str\n    })\n\n    // deletion\n    // can only do a deletion if we have enough edits remaining\n    // and if there are characters left to delete in the string\n    if (frame.str.length > 1) {\n      stack.push({\n        node: frame.node,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // deletion\n    // just removing the last character from the str\n    if (frame.str.length == 1) {\n      frame.node.final = true\n    }\n\n    // substitution\n    // can only do a substitution if we have enough edits remaining\n    // and if there are characters left to substitute\n    if (frame.str.length >= 1) {\n      if (\"*\" in frame.node.edges) {\n        var substitutionNode = frame.node.edges[\"*\"]\n      } else {\n        var substitutionNode = new lunr.TokenSet\n        frame.node.edges[\"*\"] = substitutionNode\n      }\n\n      if (frame.str.length == 1) {\n        substitutionNode.final = true\n      }\n\n      stack.push({\n        node: substitutionNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // transposition\n    // can only do a transposition if there are edits remaining\n    // and there are enough characters to transpose\n    if (frame.str.length > 1) {\n      var charA = frame.str.charAt(0),\n          charB = frame.str.charAt(1),\n          transposeNode\n\n      if (charB in frame.node.edges) {\n        transposeNode = frame.node.edges[charB]\n      } else {\n        transposeNode = new lunr.TokenSet\n        frame.node.edges[charB] = transposeNode\n      }\n\n      if (frame.str.length == 1) {\n        transposeNode.final = true\n      }\n\n      stack.push({\n        node: transposeNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: charA + frame.str.slice(2)\n      })\n    }\n  }\n\n  return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n  var node = new lunr.TokenSet,\n      root = node\n\n  /*\n   * Iterates through all characters within the passed string\n   * appending a node for each character.\n   *\n   * When a wildcard character is found then a self\n   * referencing edge is introduced to continually match\n   * any number of any characters.\n   */\n  for (var i = 0, len = str.length; i < len; i++) {\n    var char = str[i],\n        final = (i == len - 1)\n\n    if (char == \"*\") {\n      node.edges[char] = node\n      node.final = final\n\n    } else {\n      var next = new lunr.TokenSet\n      next.final = final\n\n      node.edges[char] = next\n      node = next\n    }\n  }\n\n  return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n  var words = []\n\n  var stack = [{\n    prefix: \"\",\n    node: this\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop(),\n        edges = Object.keys(frame.node.edges),\n        len = edges.length\n\n    if (frame.node.final) {\n      /* In Safari, at this point the prefix is sometimes corrupted, see:\n       * https://github.com/olivernn/lunr.js/issues/279 Calling any\n       * String.prototype method forces Safari to \"cast\" this string to what\n       * it's supposed to be, fixing the bug. */\n      frame.prefix.charAt(0)\n      words.push(frame.prefix)\n    }\n\n    for (var i = 0; i < len; i++) {\n      var edge = edges[i]\n\n      stack.push({\n        prefix: frame.prefix.concat(edge),\n        node: frame.node.edges[edge]\n      })\n    }\n  }\n\n  return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n  // NOTE: Using Object.keys here as this.edges is very likely\n  // to enter 'hash-mode' with many keys being added\n  //\n  // avoiding a for-in loop here as it leads to the function\n  // being de-optimised (at least in V8). From some simple\n  // benchmarks the performance is comparable, but allowing\n  // V8 to optimize may mean easy performance wins in the future.\n\n  if (this._str) {\n    return this._str\n  }\n\n  var str = this.final ? '1' : '0',\n      labels = Object.keys(this.edges).sort(),\n      len = labels.length\n\n  for (var i = 0; i < len; i++) {\n    var label = labels[i],\n        node = this.edges[label]\n\n    str = str + label + node.id\n  }\n\n  return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n  var output = new lunr.TokenSet,\n      frame = undefined\n\n  var stack = [{\n    qNode: b,\n    output: output,\n    node: this\n  }]\n\n  while (stack.length) {\n    frame = stack.pop()\n\n    // NOTE: As with the #toString method, we are using\n    // Object.keys and a for loop instead of a for-in loop\n    // as both of these objects enter 'hash' mode, causing\n    // the function to be de-optimised in V8\n    var qEdges = Object.keys(frame.qNode.edges),\n        qLen = qEdges.length,\n        nEdges = Object.keys(frame.node.edges),\n        nLen = nEdges.length\n\n    for (var q = 0; q < qLen; q++) {\n      var qEdge = qEdges[q]\n\n      for (var n = 0; n < nLen; n++) {\n        var nEdge = nEdges[n]\n\n        if (nEdge == qEdge || qEdge == '*') {\n          var node = frame.node.edges[nEdge],\n              qNode = frame.qNode.edges[qEdge],\n              final = node.final && qNode.final,\n              next = undefined\n\n          if (nEdge in frame.output.edges) {\n            // an edge already exists for this character\n            // no need to create a new node, just set the finality\n            // bit unless this node is already final\n            next = frame.output.edges[nEdge]\n            next.final = next.final || final\n\n          } else {\n            // no edge exists yet, must create one\n            // set the finality bit and insert it\n            // into the output\n            next = new lunr.TokenSet\n            next.final = final\n            frame.output.edges[nEdge] = next\n          }\n\n          stack.push({\n            qNode: qNode,\n            output: next,\n            node: node\n          })\n        }\n      }\n    }\n  }\n\n  return output\n}\nlunr.TokenSet.Builder = function () {\n  this.previousWord = \"\"\n  this.root = new lunr.TokenSet\n  this.uncheckedNodes = []\n  this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n  var node,\n      commonPrefix = 0\n\n  if (word < this.previousWord) {\n    throw new Error (\"Out of order word insertion\")\n  }\n\n  for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n    if (word[i] != this.previousWord[i]) break\n    commonPrefix++\n  }\n\n  this.minimize(commonPrefix)\n\n  if (this.uncheckedNodes.length == 0) {\n    node = this.root\n  } else {\n    node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n  }\n\n  for (var i = commonPrefix; i < word.length; i++) {\n    var nextNode = new lunr.TokenSet,\n        char = word[i]\n\n    node.edges[char] = nextNode\n\n    this.uncheckedNodes.push({\n      parent: node,\n      char: char,\n      child: nextNode\n    })\n\n    node = nextNode\n  }\n\n  node.final = true\n  this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n  this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n  for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n    var node = this.uncheckedNodes[i],\n        childKey = node.child.toString()\n\n    if (childKey in this.minimizedNodes) {\n      node.parent.edges[node.char] = this.minimizedNodes[childKey]\n    } else {\n      // Cache the key for this node since\n      // we know it can't change anymore\n      node.child._str = childKey\n\n      this.minimizedNodes[childKey] = node.child\n    }\n\n    this.uncheckedNodes.pop()\n  }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n  this.invertedIndex = attrs.invertedIndex\n  this.fieldVectors = attrs.fieldVectors\n  this.tokenSet = attrs.tokenSet\n  this.fields = attrs.fields\n  this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example <caption>Simple single term query</caption>\n * hello\n * @example <caption>Multiple term query</caption>\n * hello world\n * @example <caption>term scoped to a field</caption>\n * title:hello\n * @example <caption>term with a boost of 10</caption>\n * hello^10\n * @example <caption>term with an edit distance of 2</caption>\n * hello~2\n * @example <caption>terms with presence modifiers</caption>\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first.  For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n  return this.query(function (query) {\n    var parser = new lunr.QueryParser(queryString, query)\n    parser.parse()\n  })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n  // for each query clause\n  // * process terms\n  // * expand terms from token set\n  // * find matching documents and metadata\n  // * get document vectors\n  // * score documents\n\n  var query = new lunr.Query(this.fields),\n      matchingFields = Object.create(null),\n      queryVectors = Object.create(null),\n      termFieldCache = Object.create(null),\n      requiredMatches = Object.create(null),\n      prohibitedMatches = Object.create(null)\n\n  /*\n   * To support field level boosts a query vector is created per\n   * field. An empty vector is eagerly created to support negated\n   * queries.\n   */\n  for (var i = 0; i < this.fields.length; i++) {\n    queryVectors[this.fields[i]] = new lunr.Vector\n  }\n\n  fn.call(query, query)\n\n  for (var i = 0; i < query.clauses.length; i++) {\n    /*\n     * Unless the pipeline has been disabled for this term, which is\n     * the case for terms with wildcards, we need to pass the clause\n     * term through the search pipeline. A pipeline returns an array\n     * of processed terms. Pipeline functions may expand the passed\n     * term, which means we may end up performing multiple index lookups\n     * for a single query term.\n     */\n    var clause = query.clauses[i],\n        terms = null,\n        clauseMatches = lunr.Set.empty\n\n    if (clause.usePipeline) {\n      terms = this.pipeline.runString(clause.term, {\n        fields: clause.fields\n      })\n    } else {\n      terms = [clause.term]\n    }\n\n    for (var m = 0; m < terms.length; m++) {\n      var term = terms[m]\n\n      /*\n       * Each term returned from the pipeline needs to use the same query\n       * clause object, e.g. the same boost and or edit distance. The\n       * simplest way to do this is to re-use the clause object but mutate\n       * its term property.\n       */\n      clause.term = term\n\n      /*\n       * From the term in the clause we create a token set which will then\n       * be used to intersect the indexes token set to get a list of terms\n       * to lookup in the inverted index\n       */\n      var termTokenSet = lunr.TokenSet.fromClause(clause),\n          expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n      /*\n       * If a term marked as required does not exist in the tokenSet it is\n       * impossible for the search to return any matches. We set all the field\n       * scoped required matches set to empty and stop examining any further\n       * clauses.\n       */\n      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n        for (var k = 0; k < clause.fields.length; k++) {\n          var field = clause.fields[k]\n          requiredMatches[field] = lunr.Set.empty\n        }\n\n        break\n      }\n\n      for (var j = 0; j < expandedTerms.length; j++) {\n        /*\n         * For each term get the posting and termIndex, this is required for\n         * building the query vector.\n         */\n        var expandedTerm = expandedTerms[j],\n            posting = this.invertedIndex[expandedTerm],\n            termIndex = posting._index\n\n        for (var k = 0; k < clause.fields.length; k++) {\n          /*\n           * For each field that this query term is scoped by (by default\n           * all fields are in scope) we need to get all the document refs\n           * that have this term in that field.\n           *\n           * The posting is the entry in the invertedIndex for the matching\n           * term from above.\n           */\n          var field = clause.fields[k],\n              fieldPosting = posting[field],\n              matchingDocumentRefs = Object.keys(fieldPosting),\n              termField = expandedTerm + \"/\" + field,\n              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n          /*\n           * if the presence of this term is required ensure that the matching\n           * documents are added to the set of required matches for this clause.\n           *\n           */\n          if (clause.presence == lunr.Query.presence.REQUIRED) {\n            clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n            if (requiredMatches[field] === undefined) {\n              requiredMatches[field] = lunr.Set.complete\n            }\n          }\n\n          /*\n           * if the presence of this term is prohibited ensure that the matching\n           * documents are added to the set of prohibited matches for this field,\n           * creating that set if it does not yet exist.\n           */\n          if (clause.presence == lunr.Query.presence.PROHIBITED) {\n            if (prohibitedMatches[field] === undefined) {\n              prohibitedMatches[field] = lunr.Set.empty\n            }\n\n            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n            /*\n             * Prohibited matches should not be part of the query vector used for\n             * similarity scoring and no metadata should be extracted so we continue\n             * to the next field\n             */\n            continue\n          }\n\n          /*\n           * The query field vector is populated using the termIndex found for\n           * the term and a unit value with the appropriate boost applied.\n           * Using upsert because there could already be an entry in the vector\n           * for the term we are working with. In that case we just add the scores\n           * together.\n           */\n          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n          /**\n           * If we've already seen this term, field combo then we've already collected\n           * the matching documents and metadata, no need to go through all that again\n           */\n          if (termFieldCache[termField]) {\n            continue\n          }\n\n          for (var l = 0; l < matchingDocumentRefs.length; l++) {\n            /*\n             * All metadata for this term/field/document triple\n             * are then extracted and collected into an instance\n             * of lunr.MatchData ready to be returned in the query\n             * results\n             */\n            var matchingDocumentRef = matchingDocumentRefs[l],\n                matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n                metadata = fieldPosting[matchingDocumentRef],\n                fieldMatch\n\n            if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n              matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n            } else {\n              fieldMatch.add(expandedTerm, field, metadata)\n            }\n\n          }\n\n          termFieldCache[termField] = true\n        }\n      }\n    }\n\n    /**\n     * If the presence was required we need to update the requiredMatches field sets.\n     * We do this after all fields for the term have collected their matches because\n     * the clause terms presence is required in _any_ of the fields not _all_ of the\n     * fields.\n     */\n    if (clause.presence === lunr.Query.presence.REQUIRED) {\n      for (var k = 0; k < clause.fields.length; k++) {\n        var field = clause.fields[k]\n        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n      }\n    }\n  }\n\n  /**\n   * Need to combine the field scoped required and prohibited\n   * matching documents into a global set of required and prohibited\n   * matches\n   */\n  var allRequiredMatches = lunr.Set.complete,\n      allProhibitedMatches = lunr.Set.empty\n\n  for (var i = 0; i < this.fields.length; i++) {\n    var field = this.fields[i]\n\n    if (requiredMatches[field]) {\n      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n    }\n\n    if (prohibitedMatches[field]) {\n      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n    }\n  }\n\n  var matchingFieldRefs = Object.keys(matchingFields),\n      results = [],\n      matches = Object.create(null)\n\n  /*\n   * If the query is negated (contains only prohibited terms)\n   * we need to get _all_ fieldRefs currently existing in the\n   * index. This is only done when we know that the query is\n   * entirely prohibited terms to avoid any cost of getting all\n   * fieldRefs unnecessarily.\n   *\n   * Additionally, blank MatchData must be created to correctly\n   * populate the results.\n   */\n  if (query.isNegated()) {\n    matchingFieldRefs = Object.keys(this.fieldVectors)\n\n    for (var i = 0; i < matchingFieldRefs.length; i++) {\n      var matchingFieldRef = matchingFieldRefs[i]\n      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n      matchingFields[matchingFieldRef] = new lunr.MatchData\n    }\n  }\n\n  for (var i = 0; i < matchingFieldRefs.length; i++) {\n    /*\n     * Currently we have document fields that match the query, but we\n     * need to return documents. The matchData and scores are combined\n     * from multiple fields belonging to the same document.\n     *\n     * Scores are calculated by field, using the query vectors created\n     * above, and combined into a final document score using addition.\n     */\n    var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n        docRef = fieldRef.docRef\n\n    if (!allRequiredMatches.contains(docRef)) {\n      continue\n    }\n\n    if (allProhibitedMatches.contains(docRef)) {\n      continue\n    }\n\n    var fieldVector = this.fieldVectors[fieldRef],\n        score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n        docMatch\n\n    if ((docMatch = matches[docRef]) !== undefined) {\n      docMatch.score += score\n      docMatch.matchData.combine(matchingFields[fieldRef])\n    } else {\n      var match = {\n        ref: docRef,\n        score: score,\n        matchData: matchingFields[fieldRef]\n      }\n      matches[docRef] = match\n      results.push(match)\n    }\n  }\n\n  /*\n   * Sort the results objects by score, highest first.\n   */\n  return results.sort(function (a, b) {\n    return b.score - a.score\n  })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n  var invertedIndex = Object.keys(this.invertedIndex)\n    .sort()\n    .map(function (term) {\n      return [term, this.invertedIndex[term]]\n    }, this)\n\n  var fieldVectors = Object.keys(this.fieldVectors)\n    .map(function (ref) {\n      return [ref, this.fieldVectors[ref].toJSON()]\n    }, this)\n\n  return {\n    version: lunr.version,\n    fields: this.fields,\n    fieldVectors: fieldVectors,\n    invertedIndex: invertedIndex,\n    pipeline: this.pipeline.toJSON()\n  }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n  var attrs = {},\n      fieldVectors = {},\n      serializedVectors = serializedIndex.fieldVectors,\n      invertedIndex = Object.create(null),\n      serializedInvertedIndex = serializedIndex.invertedIndex,\n      tokenSetBuilder = new lunr.TokenSet.Builder,\n      pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n  if (serializedIndex.version != lunr.version) {\n    lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n  }\n\n  for (var i = 0; i < serializedVectors.length; i++) {\n    var tuple = serializedVectors[i],\n        ref = tuple[0],\n        elements = tuple[1]\n\n    fieldVectors[ref] = new lunr.Vector(elements)\n  }\n\n  for (var i = 0; i < serializedInvertedIndex.length; i++) {\n    var tuple = serializedInvertedIndex[i],\n        term = tuple[0],\n        posting = tuple[1]\n\n    tokenSetBuilder.insert(term)\n    invertedIndex[term] = posting\n  }\n\n  tokenSetBuilder.finish()\n\n  attrs.fields = serializedIndex.fields\n\n  attrs.fieldVectors = fieldVectors\n  attrs.invertedIndex = invertedIndex\n  attrs.tokenSet = tokenSetBuilder.root\n  attrs.pipeline = pipeline\n\n  return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n  this._ref = \"id\"\n  this._fields = Object.create(null)\n  this._documents = Object.create(null)\n  this.invertedIndex = Object.create(null)\n  this.fieldTermFrequencies = {}\n  this.fieldLengths = {}\n  this.tokenizer = lunr.tokenizer\n  this.pipeline = new lunr.Pipeline\n  this.searchPipeline = new lunr.Pipeline\n  this.documentCount = 0\n  this._b = 0.75\n  this._k1 = 1.2\n  this.termIndex = 0\n  this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n  this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example <caption>Extracting a nested field</caption>\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n  if (/\\//.test(fieldName)) {\n    throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n  }\n\n  this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n  if (number < 0) {\n    this._b = 0\n  } else if (number > 1) {\n    this._b = 1\n  } else {\n    this._b = number\n  }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n  this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n  var docRef = doc[this._ref],\n      fields = Object.keys(this._fields)\n\n  this._documents[docRef] = attributes || {}\n  this.documentCount += 1\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i],\n        extractor = this._fields[fieldName].extractor,\n        field = extractor ? extractor(doc) : doc[fieldName],\n        tokens = this.tokenizer(field, {\n          fields: [fieldName]\n        }),\n        terms = this.pipeline.run(tokens),\n        fieldRef = new lunr.FieldRef (docRef, fieldName),\n        fieldTerms = Object.create(null)\n\n    this.fieldTermFrequencies[fieldRef] = fieldTerms\n    this.fieldLengths[fieldRef] = 0\n\n    // store the length of this field for this document\n    this.fieldLengths[fieldRef] += terms.length\n\n    // calculate term frequencies for this field\n    for (var j = 0; j < terms.length; j++) {\n      var term = terms[j]\n\n      if (fieldTerms[term] == undefined) {\n        fieldTerms[term] = 0\n      }\n\n      fieldTerms[term] += 1\n\n      // add to inverted index\n      // create an initial posting if one doesn't exist\n      if (this.invertedIndex[term] == undefined) {\n        var posting = Object.create(null)\n        posting[\"_index\"] = this.termIndex\n        this.termIndex += 1\n\n        for (var k = 0; k < fields.length; k++) {\n          posting[fields[k]] = Object.create(null)\n        }\n\n        this.invertedIndex[term] = posting\n      }\n\n      // add an entry for this term/fieldName/docRef to the invertedIndex\n      if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n        this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n      }\n\n      // store all whitelisted metadata about this token in the\n      // inverted index\n      for (var l = 0; l < this.metadataWhitelist.length; l++) {\n        var metadataKey = this.metadataWhitelist[l],\n            metadata = term.metadata[metadataKey]\n\n        if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n          this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n        }\n\n        this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n      }\n    }\n\n  }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n  var fieldRefs = Object.keys(this.fieldLengths),\n      numberOfFields = fieldRefs.length,\n      accumulator = {},\n      documentsWithField = {}\n\n  for (var i = 0; i < numberOfFields; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        field = fieldRef.fieldName\n\n    documentsWithField[field] || (documentsWithField[field] = 0)\n    documentsWithField[field] += 1\n\n    accumulator[field] || (accumulator[field] = 0)\n    accumulator[field] += this.fieldLengths[fieldRef]\n  }\n\n  var fields = Object.keys(this._fields)\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i]\n    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n  }\n\n  this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n  var fieldVectors = {},\n      fieldRefs = Object.keys(this.fieldTermFrequencies),\n      fieldRefsLength = fieldRefs.length,\n      termIdfCache = Object.create(null)\n\n  for (var i = 0; i < fieldRefsLength; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        fieldName = fieldRef.fieldName,\n        fieldLength = this.fieldLengths[fieldRef],\n        fieldVector = new lunr.Vector,\n        termFrequencies = this.fieldTermFrequencies[fieldRef],\n        terms = Object.keys(termFrequencies),\n        termsLength = terms.length\n\n\n    var fieldBoost = this._fields[fieldName].boost || 1,\n        docBoost = this._documents[fieldRef.docRef].boost || 1\n\n    for (var j = 0; j < termsLength; j++) {\n      var term = terms[j],\n          tf = termFrequencies[term],\n          termIndex = this.invertedIndex[term]._index,\n          idf, score, scoreWithPrecision\n\n      if (termIdfCache[term] === undefined) {\n        idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n        termIdfCache[term] = idf\n      } else {\n        idf = termIdfCache[term]\n      }\n\n      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n      score *= fieldBoost\n      score *= docBoost\n      scoreWithPrecision = Math.round(score * 1000) / 1000\n      // Converts 1.23456789 to 1.234.\n      // Reducing the precision so that the vectors take up less\n      // space when serialised. Doing it now so that they behave\n      // the same before and after serialisation. Also, this is\n      // the fastest approach to reducing a number's precision in\n      // JavaScript.\n\n      fieldVector.insert(termIndex, scoreWithPrecision)\n    }\n\n    fieldVectors[fieldRef] = fieldVector\n  }\n\n  this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n  this.tokenSet = lunr.TokenSet.fromArray(\n    Object.keys(this.invertedIndex).sort()\n  )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n  this.calculateAverageFieldLengths()\n  this.createFieldVectors()\n  this.createTokenSet()\n\n  return new lunr.Index({\n    invertedIndex: this.invertedIndex,\n    fieldVectors: this.fieldVectors,\n    tokenSet: this.tokenSet,\n    fields: Object.keys(this._fields),\n    pipeline: this.searchPipeline\n  })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n  var args = Array.prototype.slice.call(arguments, 1)\n  args.unshift(this)\n  fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n  var clonedMetadata = Object.create(null),\n      metadataKeys = Object.keys(metadata || {})\n\n  // Cloning the metadata to prevent the original\n  // being mutated during match data combination.\n  // Metadata is kept in an array within the inverted\n  // index so cloning the data can be done with\n  // Array#slice\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n    clonedMetadata[key] = metadata[key].slice()\n  }\n\n  this.metadata = Object.create(null)\n\n  if (term !== undefined) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = clonedMetadata\n  }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n  var terms = Object.keys(otherMatchData.metadata)\n\n  for (var i = 0; i < terms.length; i++) {\n    var term = terms[i],\n        fields = Object.keys(otherMatchData.metadata[term])\n\n    if (this.metadata[term] == undefined) {\n      this.metadata[term] = Object.create(null)\n    }\n\n    for (var j = 0; j < fields.length; j++) {\n      var field = fields[j],\n          keys = Object.keys(otherMatchData.metadata[term][field])\n\n      if (this.metadata[term][field] == undefined) {\n        this.metadata[term][field] = Object.create(null)\n      }\n\n      for (var k = 0; k < keys.length; k++) {\n        var key = keys[k]\n\n        if (this.metadata[term][field][key] == undefined) {\n          this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n        } else {\n          this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n        }\n\n      }\n    }\n  }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n  if (!(term in this.metadata)) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  if (!(field in this.metadata[term])) {\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  var metadataKeys = Object.keys(metadata)\n\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n\n    if (key in this.metadata[term][field]) {\n      this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n    } else {\n      this.metadata[term][field][key] = metadata[key]\n    }\n  }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n  this.clauses = []\n  this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with trailing wildcard</caption>\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example <caption>query term with leading and trailing wildcard</caption>\n * query.term('foo', {\n *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with required presence</caption>\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n  /**\n   * Term's presence in a document is optional, this is the default value.\n   */\n  OPTIONAL: 1,\n\n  /**\n   * Term's presence in a document is required, documents that do not contain\n   * this term will not be returned.\n   */\n  REQUIRED: 2,\n\n  /**\n   * Term's presence in a document is prohibited, documents that do contain\n   * this term will not be returned.\n   */\n  PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n  if (!('fields' in clause)) {\n    clause.fields = this.allFields\n  }\n\n  if (!('boost' in clause)) {\n    clause.boost = 1\n  }\n\n  if (!('usePipeline' in clause)) {\n    clause.usePipeline = true\n  }\n\n  if (!('wildcard' in clause)) {\n    clause.wildcard = lunr.Query.wildcard.NONE\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n    clause.term = \"*\" + clause.term\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n    clause.term = \"\" + clause.term + \"*\"\n  }\n\n  if (!('presence' in clause)) {\n    clause.presence = lunr.Query.presence.OPTIONAL\n  }\n\n  this.clauses.push(clause)\n\n  return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n  for (var i = 0; i < this.clauses.length; i++) {\n    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n      return false\n    }\n  }\n\n  return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example <caption>adding a single term to a query</caption>\n * query.term(\"foo\")\n * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>\n * query.term(\"foo\", {\n *   fields: [\"title\"],\n *   boost: 10,\n *   wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n  if (Array.isArray(term)) {\n    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n    return this\n  }\n\n  var clause = options || {}\n  clause.term = term.toString()\n\n  this.clause(clause)\n\n  return this\n}\nlunr.QueryParseError = function (message, start, end) {\n  this.name = \"QueryParseError\"\n  this.message = message\n  this.start = start\n  this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n  this.lexemes = []\n  this.str = str\n  this.length = str.length\n  this.pos = 0\n  this.start = 0\n  this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n  var state = lunr.QueryLexer.lexText\n\n  while (state) {\n    state = state(this)\n  }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n  var subSlices = [],\n      sliceStart = this.start,\n      sliceEnd = this.pos\n\n  for (var i = 0; i < this.escapeCharPositions.length; i++) {\n    sliceEnd = this.escapeCharPositions[i]\n    subSlices.push(this.str.slice(sliceStart, sliceEnd))\n    sliceStart = sliceEnd + 1\n  }\n\n  subSlices.push(this.str.slice(sliceStart, this.pos))\n  this.escapeCharPositions.length = 0\n\n  return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n  this.lexemes.push({\n    type: type,\n    str: this.sliceString(),\n    start: this.start,\n    end: this.pos\n  })\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n  this.escapeCharPositions.push(this.pos - 1)\n  this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n  if (this.pos >= this.length) {\n    return lunr.QueryLexer.EOS\n  }\n\n  var char = this.str.charAt(this.pos)\n  this.pos += 1\n  return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n  return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n  if (this.start == this.pos) {\n    this.pos += 1\n  }\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n  this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n  var char, charCode\n\n  do {\n    char = this.next()\n    charCode = char.charCodeAt(0)\n  } while (charCode > 47 && charCode < 58)\n\n  if (char != lunr.QueryLexer.EOS) {\n    this.backup()\n  }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n  return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n  lexer.backup()\n  lexer.emit(lunr.QueryLexer.FIELD)\n  lexer.ignore()\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n  if (lexer.width() > 1) {\n    lexer.backup()\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n\n  lexer.ignore()\n\n  if (lexer.more()) {\n    return lunr.QueryLexer.lexText\n  }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.BOOST)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n  if (lexer.width() > 0) {\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n  while (true) {\n    var char = lexer.next()\n\n    if (char == lunr.QueryLexer.EOS) {\n      return lunr.QueryLexer.lexEOS\n    }\n\n    // Escape character is '\\'\n    if (char.charCodeAt(0) == 92) {\n      lexer.escapeCharacter()\n      continue\n    }\n\n    if (char == \":\") {\n      return lunr.QueryLexer.lexField\n    }\n\n    if (char == \"~\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexEditDistance\n    }\n\n    if (char == \"^\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexBoost\n    }\n\n    // \"+\" indicates term presence is required\n    // checking for length to ensure that only\n    // leading \"+\" are considered\n    if (char == \"+\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    // \"-\" indicates term presence is prohibited\n    // checking for length to ensure that only\n    // leading \"-\" are considered\n    if (char == \"-\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    if (char.match(lunr.QueryLexer.termSeparator)) {\n      return lunr.QueryLexer.lexTerm\n    }\n  }\n}\n\nlunr.QueryParser = function (str, query) {\n  this.lexer = new lunr.QueryLexer (str)\n  this.query = query\n  this.currentClause = {}\n  this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n  this.lexer.run()\n  this.lexemes = this.lexer.lexemes\n\n  var state = lunr.QueryParser.parseClause\n\n  while (state) {\n    state = state(this)\n  }\n\n  return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n  return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n  var lexeme = this.peekLexeme()\n  this.lexemeIdx += 1\n  return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n  var completedClause = this.currentClause\n  this.query.clause(completedClause)\n  this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n  var lexeme = parser.peekLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.type) {\n    case lunr.QueryLexer.PRESENCE:\n      return lunr.QueryParser.parsePresence\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n      if (lexeme.str.length >= 1) {\n        errorMessage += \" with value '\" + lexeme.str + \"'\"\n      }\n\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.str) {\n    case \"-\":\n      parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n      break\n    case \"+\":\n      parser.currentClause.presence = lunr.Query.presence.REQUIRED\n      break\n    default:\n      var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term or field, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n    var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n        errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.fields = [lexeme.str]\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  parser.currentClause.term = lexeme.str.toLowerCase()\n\n  if (lexeme.str.indexOf(\"*\") != -1) {\n    parser.currentClause.usePipeline = false\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var editDistance = parseInt(lexeme.str, 10)\n\n  if (isNaN(editDistance)) {\n    var errorMessage = \"edit distance must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.editDistance = editDistance\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var boost = parseInt(lexeme.str, 10)\n\n  if (isNaN(boost)) {\n    var errorMessage = \"boost must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.boost = boost\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\n  /**\n   * export the module via AMD, CommonJS or as a browser global\n   * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n   */\n  ;(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n      // AMD. Register as an anonymous module.\n      define(factory)\n    } else if (typeof exports === 'object') {\n      /**\n       * Node. Does not work with strict CommonJS, but\n       * only CommonJS-like enviroments that support module.exports,\n       * like Node.\n       */\n      module.exports = factory()\n    } else {\n      // Browser globals (root is window)\n      root.lunr = factory()\n    }\n  }(this, function () {\n    /**\n     * Just return a value to define the module export.\n     * This example returns an object, but the module\n     * can return a function as the exported value.\n     */\n    return lunr\n  }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n  function importScripts(...urls: string[]): Promise<void> | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n  config: SearchIndexConfig\n): Promise<void> {\n  let base = \"../lunr\"\n\n  /* Detect `iframe-worker` and fix base URL */\n  if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n    const worker = document.querySelector<HTMLScriptElement>(\"script[src]\")!\n    const [path] = worker.src.split(\"/worker\")\n\n    /* Prefix base with path */\n    base = base.replace(\"..\", path)\n  }\n\n  /* Add scripts for languages */\n  const scripts = []\n  for (const lang of config.lang) {\n    if (lang === \"ja\") scripts.push(`${base}/tinyseg.js`)\n    if (lang !== \"en\") scripts.push(`${base}/min/lunr.${lang}.min.js`)\n  }\n\n  /* Add multi-language support */\n  if (config.lang.length > 1)\n    scripts.push(`${base}/min/lunr.multi.min.js`)\n\n  /* Load scripts synchronously */\n  if (scripts.length)\n    await importScripts(\n      `${base}/min/lunr.stemmer.support.min.js`,\n      ...scripts\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n  message: SearchMessage\n): Promise<SearchMessage> {\n  switch (message.type) {\n\n    /* Search setup message */\n    case SearchMessageType.SETUP:\n      await setupSearchLanguages(message.data.config)\n      index = new Search(message.data)\n      return {\n        type: SearchMessageType.READY\n      }\n\n    /* Search query message */\n    case SearchMessageType.QUERY:\n      return {\n        type: SearchMessageType.RESULT,\n        data: index ? index.search(message.data) : []\n      }\n\n    /* All other messages */\n    default:\n      throw new TypeError(\"Invalid message type\")\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-ignore - expose Lunr.js in global scope, or stemmers will not work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n  postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n  config: SearchIndexConfig\n): SearchHighlightFactoryFn {\n  const separator = new RegExp(config.separator, \"img\")\n  const highlight = (_: unknown, data: string, term: string) => {\n    return `${data}<mark data-md-highlight>${term}</mark>`\n  }\n\n  /* Return factory function */\n  return (query: string) => {\n    query = query\n      .replace(/[\\s*+\\-:~^]+/g, \" \")\n      .trim()\n\n    /* Create search term match expression */\n    const match = new RegExp(`(^|${config.separator})(${\n      query\n        .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n        .replace(separator, \"|\")\n    })`, \"img\")\n\n    /* Highlight string value */\n    return value => value\n      .replace(match, highlight)\n      .replace(/<\\/mark>(\\s+)<mark[^>]*>/img, \"$1\")\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n  presence: lunr.Query.presence        /* Clause presence */\n  term: string                         /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record<string, boolean>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n  value: string\n): SearchQueryClause[] {\n  const query  = new (lunr as any).Query([\"title\", \"text\"])\n  const parser = new (lunr as any).QueryParser(value, query)\n\n  /* Parse and return query clauses */\n  parser.parse()\n  return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n  query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n  const clauses = new Set<SearchQueryClause>(query)\n\n  /* Match query clauses against terms */\n  const result: SearchQueryTerms = {}\n  for (let t = 0; t < terms.length; t++)\n    for (const clause of clauses)\n      if (terms[t].startsWith(clause.term)) {\n        result[clause.term] = true\n        clauses.delete(clause)\n      }\n\n  /* Annotate unmatched query clauses */\n  for (const clause of clauses)\n    result[clause.term] = false\n\n  /* Return query terms */\n  return result\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  SearchDocument,\n  SearchDocumentMap,\n  setupSearchDocumentMap\n} from \"../document\"\nimport {\n  SearchHighlightFactoryFn,\n  setupSearchHighlighter\n} from \"../highlighter\"\nimport {\n  SearchQueryTerms,\n  getSearchQueryTerms,\n  parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n  lang: string[]                       /* Search languages */\n  separator: string                    /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n  location: string                     /* Document location */\n  title: string                        /* Document title */\n  text: string                         /* Document text */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index pipeline function\n */\nexport type SearchIndexPipelineFn =\n  | \"trimmer\"                          /* Trimmer */\n  | \"stopWordFilter\"                   /* Stop word filter */\n  | \"stemmer\"                          /* Stemmer */\n\n/**\n * Search index pipeline\n */\nexport type SearchIndexPipeline = SearchIndexPipelineFn[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n  config: SearchIndexConfig            /* Search index configuration */\n  docs: SearchIndexDocument[]          /* Search index documents */\n  index?: object                       /* Prebuilt index */\n  pipeline?: SearchIndexPipeline       /* Search index pipeline */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n  score: number                        /* Score (relevance) */\n  terms: SearchQueryTerms              /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport type SearchResult = Array<SearchDocument & SearchMetadata>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n  const [x, y] = [new Set(a), new Set(b)]\n  return [\n    ...new Set([...x].filter(value => !y.has(value)))\n  ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n  /**\n   * Search document mapping\n   *\n   * A mapping of URLs (including hash fragments) to the actual articles and\n   * sections of the documentation. The search document mapping must be created\n   * regardless of whether the index was prebuilt or not, as Lunr.js itself\n   * only stores the actual index.\n   */\n  protected documents: SearchDocumentMap\n\n  /**\n   * Search highlight factory function\n   */\n  protected highlight: SearchHighlightFactoryFn\n\n  /**\n   * The underlying Lunr.js search index\n   */\n  protected index: lunr.Index\n\n  /**\n   * Create the search integration\n   *\n   * @param data - Search index\n   */\n  public constructor({ config, docs, pipeline, index }: SearchIndex) {\n    this.documents = setupSearchDocumentMap(docs)\n    this.highlight = setupSearchHighlighter(config)\n\n    /* Set separator for tokenizer */\n    lunr.tokenizer.separator = new RegExp(config.separator)\n\n    /* If no index was given, create it */\n    if (typeof index === \"undefined\") {\n      this.index = lunr(function () {\n\n        /* Set up multi-language support */\n        if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n          this.use((lunr as any)[config.lang[0]])\n        } else if (config.lang.length > 1) {\n          this.use((lunr as any).multiLanguage(...config.lang))\n        }\n\n        /* Compute functions to be removed from the pipeline */\n        const fns = difference([\n          \"trimmer\", \"stopWordFilter\", \"stemmer\"\n        ], pipeline!)\n\n        /* Remove functions from the pipeline for registered languages */\n        for (const lang of config.lang.map(language => (\n          language === \"en\" ? lunr : (lunr as any)[language]\n        ))) {\n          for (const fn of fns) {\n            this.pipeline.remove(lang[fn])\n            this.searchPipeline.remove(lang[fn])\n          }\n        }\n\n        /* Set up fields and reference */\n        this.field(\"title\", { boost: 1000 })\n        this.field(\"text\")\n        this.ref(\"location\")\n\n        /* Index documents */\n        for (const doc of docs)\n          this.add(doc)\n      })\n\n    /* Handle prebuilt index */\n    } else {\n      this.index = lunr.Index.load(index)\n    }\n  }\n\n  /**\n   * Search for matching documents\n   *\n   * The search index which MkDocs provides is divided up into articles, which\n   * contain the whole content of the individual pages, and sections, which only\n   * contain the contents of the subsections obtained by breaking the individual\n   * pages up at `h1` ... `h6`. As there may be many sections on different pages\n   * with identical titles (for example within this very project, e.g. \"Usage\"\n   * or \"Installation\"), they need to be put into the context of the containing\n   * page. For this reason, section results are grouped within their respective\n   * articles which are the top-level results that are returned.\n   *\n   * @param query - Query value\n   *\n   * @returns Search results\n   */\n  public search(query: string): SearchResult[] {\n    if (query) {\n      try {\n        const highlight = this.highlight(query)\n\n        /* Parse query to extract clauses for analysis */\n        const clauses = parseSearchQuery(query)\n          .filter(clause => (\n            clause.presence !== lunr.Query.presence.PROHIBITED\n          ))\n\n        /* Perform search and post-process results */\n        const groups = this.index.search(`${query}*`)\n\n          /* Apply post-query boosts based on title and search query terms */\n          .reduce<SearchResult>((results, { ref, score, matchData }) => {\n            const document = this.documents.get(ref)\n            if (typeof document !== \"undefined\") {\n              const { location, title, text, parent } = document\n\n              /* Compute and analyze search query terms */\n              const terms = getSearchQueryTerms(\n                clauses,\n                Object.keys(matchData.metadata)\n              )\n\n              /* Highlight title and text and apply post-query boosts */\n              const boost = +!parent + +Object.values(terms).every(t => t)\n              results.push({\n                location,\n                title: highlight(title),\n                text: highlight(text),\n                score: score * (1 + boost),\n                terms\n              })\n            }\n            return results\n          }, [])\n\n          /* Sort search results again after applying boosts */\n          .sort((a, b) => b.score - a.score)\n\n          /* Group search results by page */\n          .reduce((results, result) => {\n            const document = this.documents.get(result.location)\n            if (typeof document !== \"undefined\") {\n              const ref = \"parent\" in document\n                ? document.parent!.location\n                : document.location\n              results.set(ref, [...results.get(ref) || [], result])\n            }\n            return results\n          }, new Map<string, SearchResult>())\n\n        /* Expand grouped search results */\n        return [...groups.values()]\n\n      /* Log errors to console (for now) */\n      } catch {\n        console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n      }\n    }\n\n    /* Return nothing in case of error or empty query */\n    return []\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n"],
+  "mappings": "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AAG,GACV,EAAK,EACD,EAAG,KAAK,IACV,GAAI,WAEG,EAAI,KAAK,GAAI,CACtB,GAAI,GAAK,EAAI,KAAK,GAClB,EAAO,EAAG,GAAK,EAAG,GAClB,EAAM,EACF,EAAI,KAAK,IACX,GAAI,GAMR,GADA,EAAK,EACD,EAAG,KAAK,GAAI,CACd,GAAI,GAAK,EAAG,KAAK,GACjB,EAAO,EAAG,GACV,EAAK,EACL,EAAM,EACN,EAAM,EACF,GAAG,KAAK,IAAU,EAAI,KAAK,IAAS,CAAE,EAAI,KAAK,KACjD,GAAI,GAIR,SAAK,EACL,EAAM,EACF,EAAG,KAAK,IAAM,EAAI,KAAK,IACzB,GAAK,EACL,EAAI,EAAE,QAAQ,EAAG,KAKf,GAAW,KACb,GAAI,EAAQ,cAAgB,EAAE,OAAO,IAGhC,GAGT,MAAO,UAAU,EAAO,CACtB,MAAO,GAAM,OAAO,OAIxB,EAAK,SAAS,iBAAiB,EAAK,QAAS,WAC7C,AAkBA,EAAK,uBAAyB,SAAU,EAAW,CACjD,GAAI,GAAQ,EAAU,OAAO,SAAU,EAAM,EAAU,CACrD,SAAK,GAAY,EACV,GACN,IAEH,MAAO,UAAU,EAAO,CACtB,GAAI,GAAS,EAAM,EAAM,cAAgB,EAAM,WAAY,MAAO,KAiBtE,EAAK,eAAiB,EAAK,uBAAuB,CAChD,IACA,OACA,QACA,SACA,QACA,MACA,SACA,OACA,KACA,QACA,KACA,MACA,MACA,MACA,KACA,KACA,KACA,UACA,OACA,MACA,KACA,MACA,SACA,QACA,OACA,MACA,KACA,OACA,SACA,OACA,OACA,QACA,MACA,OACA,MACA,MACA,MACA,MACA,OACA,KACA,MACA,OACA,MACA,MACA,MACA,UACA,IACA,KACA,KACA,OACA,KACA,KACA,MACA,OACA,QACA,MACA,OACA,SACA,MACA,KACA,QACA,OACA,OACA,KACA,UACA,KACA,MACA,MACA,KACA,MACA,QACA,KACA,OACA,KACA,QACA,MACA,MACA,SACA,OACA,MACA,OACA,MACA,SACA,QACA,KACA,OACA,OACA,OACA,MACA,QACA,OACA,OACA,QACA,QACA,OACA,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",
+  "names": []
+}
diff --git a/5.4/assets/stylesheets/main.33e2939f.min.css b/5.4/assets/stylesheets/main.33e2939f.min.css
new file mode 100644 (file)
index 0000000..54d2163
--- /dev/null
@@ -0,0 +1,2 @@
+@charset "UTF-8";html{box-sizing:border-box;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}*,:after,:before{box-sizing:inherit}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{display:block;box-sizing:initial;height:.05rem;padding:0;overflow:visible;border:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;font-size:inherit;background:transparent;border:0}input{border:0;outline:none}:root{--md-default-fg-color:rgba(0,0,0,0.87);--md-default-fg-color--light:rgba(0,0,0,0.54);--md-default-fg-color--lighter:rgba(0,0,0,0.32);--md-default-fg-color--lightest:rgba(0,0,0,0.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,0.7);--md-default-bg-color--lighter:hsla(0,0%,100%,0.3);--md-default-bg-color--lightest:hsla(0,0%,100%,0.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}:root>*{--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,0.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,0.5);--md-typeset-del-color:rgba(245,80,61,0.15);--md-typeset-ins-color:rgba(11,213,112,0.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,0.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,0.3);--md-footer-bg-color:rgba(0,0,0,0.87);--md-footer-bg-color--dark:rgba(0,0,0,0.32)}.md-icon svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family,_),SFMono-Regular,Consolas,Menlo,monospace}:root{--md-typeset-table--ascending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 4h2v12l5.5-5.5 1.42 1.42L12 19.84l-7.92-7.92L5.5 10.5 11 16V4z'/></svg>");--md-typeset-table--descending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z'/></svg>")}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact;color-adjust:exact}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{display:flow-root;margin:1em 0}.md-typeset h1{margin:0 0 1.25em;color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:1.6em 0 .64em;font-size:1.5625em;line-height:1.4}.md-typeset h3{margin:1.6em 0 .8em;font-weight:400;font-size:1.25em;line-height:1.5;letter-spacing:-.01em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{margin:1em 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{margin:1.25em 0;color:var(--md-default-fg-color--light);font-weight:700;font-size:.8em;letter-spacing:-.01em}.md-typeset h5{text-transform:uppercase}.md-typeset hr{display:flow-root;margin:1.5em 0;border-bottom:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{padding:0 .2941176471em;font-size:.85em;word-break:break-word;background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset code:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:initial;padding:initial;background-color:initial;box-shadow:none}.md-typeset a code{color:currentColor}.md-typeset pre{position:relative;line-height:1.4}.md-typeset pre>code{display:block;margin:0;padding:.7720588235em 1.1764705882em;overflow:auto;word-break:normal;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;touch-action:auto;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:44.9375em){.md-typeset>pre{margin:1em -.8rem}.md-typeset>pre code{border-radius:0}}.md-typeset kbd{display:inline-block;padding:0 .6666666667em;color:var(--md-default-fg-color);font-size:.75em;vertical-align:text-top;word-break:break-word;background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset}.md-typeset mark{color:inherit;word-break:break-word;background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{text-decoration:none;border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:focus:after,.md-typeset abbr[title]:hover:after{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:absolute;left:0;display:inline-block;width:auto;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;max-width:80%;margin-top:2em;padding:.2rem .3rem;color:var(--md-default-bg-color);font-size:.7rem;background-color:var(--md-default-fg-color);border-radius:.1rem;content:attr(title)}}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;color:var(--md-default-fg-color--light);border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid var(--md-default-fg-color--lighter);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1.5em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset img,.md-typeset svg{max-width:100%;height:auto}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset figure{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;margin:0 auto;text-align:center}.md-typeset figure img{display:block}.md-typeset figcaption{max-width:24rem;margin:1em auto 2em;font-style:italic}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){display:inline-block;max-width:100%;overflow:auto;font-size:.64rem;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .05rem rgba(0,0,0,.1);touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.9375em 1.25em;color:var(--md-default-bg-color);vertical-align:top;background-color:var(--md-default-fg-color--light)}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{padding:.9375em 1.25em;vertical-align:top;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset table:not([class]) tr{transition:background-color 125ms}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}.md-typeset table th[role=columnheader]:after{display:inline-block;width:1.2em;height:1.2em;margin-left:.5em;vertical-align:sub;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--ascending);mask-image:var(--md-typeset-table--ascending)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--descending);mask-image:var(--md-typeset-table--descending)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{height:100%;overflow-x:hidden;font-size:125%}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;font-size:.5rem;background-color:var(--md-default-bg-color)}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-state=lock]{position:fixed}}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.md-toggle{display:none}.md-option{position:absolute;width:0;height:0;opacity:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-style:auto}.md-skip{position:fixed;z-index:-1;margin:.5rem;padding:.3rem .5rem;color:var(--md-default-bg-color);font-size:.64rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(.4rem);opacity:0}.md-skip:focus{z-index:10;transform:translateY(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms}@page{margin:25mm}.md-announce{overflow:auto;background-color:var(--md-footer-bg-color)}@media print{.md-announce{display:none}}.md-announce__inner{margin:.6rem auto;padding:0 .8rem;color:var(--md-footer-fg-color);font-size:.7rem}:root{--md-clipboard-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1z'/></svg>")}.md-clipboard{position:absolute;top:.5em;right:.5em;z-index:1;width:1.5em;height:1.5em;color:var(--md-default-fg-color--lightest);border-radius:.1rem;cursor:pointer;transition:color .25s}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{display:block;width:1.125em;height:1.125em;margin:0 auto;background-color:currentColor;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-content{flex-grow:1;overflow:hidden;scroll-padding-top:51.2rem}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){.md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem;margin-left:.8rem}.md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:.8rem;margin-left:1.2rem}}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__button{float:right;margin:.4rem 0 .4rem .4rem;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left;margin-right:.4rem;margin-left:0}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}.md-dialog{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:fixed;right:.8rem;bottom:.8rem;left:auto;z-index:2;min-width:11.1rem;padding:.4rem .6rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(100%);opacity:0;transition:transform 0ms .4s,opacity .4s;pointer-events:none}@media print{.md-dialog{display:none}}[dir=rtl] .md-dialog{right:auto;left:.8rem}.md-dialog[data-md-state=open]{transform:translateY(0);opacity:1;transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s;pointer-events:auto}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-typeset .md-button{display:inline-block;padding:.625em 2em;color:var(--md-primary-fg-color);font-weight:700;border:.1rem solid;border-radius:.1rem;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{color:var(--md-accent-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .md-input{height:1.8rem;padding:0 .6rem;font-size:.8rem;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transition:box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{box-shadow:0 .4rem 1rem rgba(0,0,0,.15),0 .025rem .05rem rgba(0,0,0,.15)}.md-typeset .md-input--stretch{width:100%}.md-header{position:-webkit-sticky;position:sticky;top:0;right:0;left:0;z-index:2;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent}@media print{.md-header{display:none}}.md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header[data-md-state=hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header .focus-visible{outline-color:currentColor}.md-header__inner{display:flex;align-items:center;padding:0 .2rem}.md-header__button{position:relative;z-index:1;margin:.2rem;padding:.4rem;color:currentColor;vertical-align:middle;cursor:pointer;transition:opacity .25s}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{position:absolute;display:flex;max-width:100%;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-header__topic+.md-header__topic{z-index:-1;transform:translateX(1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__title{flex-grow:1;height:2.4rem;margin-right:.4rem;margin-left:1rem;font-size:.9rem;line-height:2.4rem}.md-header__title[data-md-state=active] .md-header__topic{z-index:-1;transform:translateX(-1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic{transform:translateX(1.25rem)}.md-header__title[data-md-state=active] .md-header__topic+.md-header__topic{z-index:0;transform:translateX(0);opacity:1;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:auto}.md-header__title>.md-header__ellipsis{position:relative;width:100%;height:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;white-space:nowrap;transition:max-width 0ms .25s,opacity .25s .25s}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){.md-header__source{display:block;width:11.7rem;max-width:11.7rem;margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem;margin-left:0}}@media screen and (min-width:76.25em){.md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-footer{color:var(--md-footer-fg-color);background-color:var(--md-footer-bg-color)}@media print{.md-footer{display:none}}.md-footer__inner{padding:.2rem;overflow:auto}.md-footer__link{display:flex;padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}@media screen and (min-width:45em){.md-footer__link{width:50%}}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}.md-footer__link--prev{float:left}@media screen and (max-width:44.9375em){.md-footer__link--prev{width:25%}.md-footer__link--prev .md-footer__title{display:none}}[dir=rtl] .md-footer__link--prev{float:right}[dir=rtl] .md-footer__link--prev svg{transform:scaleX(-1)}.md-footer__link--next{float:right;text-align:right}@media screen and (max-width:44.9375em){.md-footer__link--next{width:75%}}[dir=rtl] .md-footer__link--next{float:left;text-align:left}[dir=rtl] .md-footer__link--next svg{transform:scaleX(-1)}.md-footer__title{position:relative;flex-grow:1;max-width:calc(100% - 2.4rem);padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-footer-copyright{width:100%;margin:auto .6rem;padding:.4rem 0;color:var(--md-footer-fg-color--lighter);font-size:.64rem}@media screen and (min-width:45em){.md-footer-copyright{width:auto}}.md-footer-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-footer-social{padding:.6rem 0}}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-footer-social__link svg{max-height:.8rem;vertical-align:-25%;fill:currentColor}:root{--md-nav-icon--prev:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z'/></svg>");--md-nav-icon--next:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>");--md-toc-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9h14V7H3v2m0 4h14v-2H3v2m0 4h14v-2H3v2m16 0h2v-2h-2v2m0-10v2h2V7h-2m0 6h2v-2h-2v2z'/></svg>")}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{width:auto;height:100%}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{display:block;width:2.4rem;height:2.4rem;fill:currentColor}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__link{display:block;margin-top:.625em;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:color 125ms;scroll-snap-align:start}.md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{display:block;width:100%;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);background-color:currentColor}.md-nav--primary .md-nav__link[for=__toc]~.md-nav,.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{position:absolute;top:0;right:0;left:0;z-index:1;display:flex;flex-direction:column;height:100%;background-color:var(--md-default-bg-color)}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;color:var(--md-default-fg-color--light);font-weight:400;line-height:2.4rem;white-space:nowrap;background-color:var(--md-default-fg-color--lightest);cursor:pointer}.md-nav--primary .md-nav__title .md-nav__icon{position:absolute;top:.4rem;left:.4rem;display:block;width:1.2rem;height:1.2rem;margin:.2rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem;left:auto}.md-nav--primary .md-nav__title .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav--primary .md-nav__title~.md-nav__list{overflow-y:auto;background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-nav--primary .md-nav__title .md-logo{position:absolute;top:.2rem;left:.2rem;display:block;margin:.2rem;padding:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-logo{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link .md-nav__icon{position:absolute;top:50%;right:.6rem;width:1.2rem;height:1.2rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{right:auto;left:.6rem}.md-nav--primary .md-nav__link .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:initial}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;transform:translateX(100%);opacity:0;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{transform:translateX(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{display:block;padding:0 .2rem;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color--dark)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:block;padding-right:2.4rem;scroll-snap-align:none}[dir=rtl] .md-nav--integrated .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav,.md-nav__toggle:indeterminate~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__title{display:block;padding:0;pointer-events:none;scroll-snap-align:start}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{float:right;width:.9rem;height:.9rem;transition:transform .25s}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:after{display:inline-block;width:100%;height:100%;vertical-align:-.1rem;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav>.md-nav__title{display:block;padding:0 .6rem;pointer-events:none;scroll-snap-align:start}.md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav__item{padding-right:.6rem}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:block;margin-bottom:1.25em;border-left:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav>.md-nav__title{display:none}}:root{--md-search-result-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7c-.41-.25-.8-.56-1.14-.9-.33-.33-.61-.7-.86-1.1H6V4h7v5h5v1.18c.71.16 1.39.43 2 .82V8l-6-6m6.31 16.9c1.33-2.11.69-4.9-1.4-6.22-2.11-1.33-4.91-.68-6.22 1.4-1.34 2.11-.69 4.89 1.4 6.22 1.46.93 3.32.93 4.79.02L22 23.39 23.39 22l-3.08-3.1m-3.81.1a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5z'/></svg>")}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{z-index:1;opacity:0}@media screen and (max-width:59.9375em){.md-search__overlay{position:absolute;top:.2rem;left:-2.2rem;width:2rem;height:2rem;overflow:hidden;background-color:var(--md-default-bg-color);border-radius:1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;pointer-events:none}[dir=rtl] .md-search__overlay{right:-2.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:rgba(0,0,0,.54);cursor:pointer;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){.md-search__inner{position:fixed;top:0;left:100%;z-index:2;width:100%;height:100%;transform:translateX(5%);opacity:0;transition:right 0ms .3s,left 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;transform:translateX(0);opacity:1;transition:right 0ms 0ms,left 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;transform:translateX(-5%)}}@media screen and (min-width:60em){.md-search__inner{position:relative;float:right;width:11.7rem;padding:.1rem 0;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{position:relative}@media screen and (min-width:60em){.md-search__form{border-radius:.1rem}}.md-search__input{position:relative;z-index:2;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;transition:color .25s,background-color .25s,box-shadow .25s}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s;transition:color .25s}.md-search__input::-moz-placeholder{-moz-transition:color .25s;transition:color .25s}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}[data-md-toggle=search]:checked~.md-header .md-search__input{box-shadow:0 0 .6rem rgba(0,0,0,.07)}@media screen and (max-width:59.9375em){.md-search__input{width:100%;height:2.4rem;font-size:.9rem}}@media screen and (min-width:60em){.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;color:inherit;font-size:.8rem;background-color:rgba(0,0,0,.26);border-radius:.1rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}.md-search__input::-webkit-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{color:var(--md-default-fg-color);text-overflow:clip;background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{position:absolute;z-index:2;width:1.2rem;height:1.2rem;cursor:pointer;transition:color .25s,opacity .25s}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){.md-search__icon[for=__search]{top:.6rem;left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem;left:auto}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}.md-search__icon[type=reset]{top:.3rem;right:.5rem;transform:scale(.75);opacity:0;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:none}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}@media screen and (max-width:59.9375em){.md-search__icon[type=reset]{top:.6rem;right:.8rem}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{transform:scale(1);opacity:1;pointer-events:auto}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;z-index:1;width:100%;overflow:hidden;border-radius:0 0 .1rem .1rem}@media screen and (max-width:59.9375em){.md-search__output{top:2.4rem;bottom:0}}@media screen and (min-width:60em){.md-search__output{top:1.9rem;opacity:0;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}}.md-search__scrollwrap{height:100%;overflow-y:auto;background-color:var(--md-default-bg-color);-webkit-backface-visibility:hidden;backface-visibility:hidden;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1), (max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{padding:0 .8rem;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;background-color:var(--md-default-fg-color--lightest);scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}}.md-search-result__list{margin:0;padding:0;list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;transition:background-color .25s;scroll-snap-align:start}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{display:block;padding:.75em .8rem;color:var(--md-typeset-a-color);font-size:.64rem;outline:0;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__more summary:focus,.md-search-result__more summary:hover{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-search-result__more summary::-webkit-details-marker,.md-search-result__more summary::marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{position:relative;padding:0 .8rem;overflow:hidden}@media screen and (min-width:60em){.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-weight:400;font-size:.8rem;line-height:1.4}.md-search-result__icon{position:absolute;left:0;width:1.2rem;height:1.2rem;margin:.5rem;color:var(--md-default-fg-color--light)}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{display:inline-block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-search-result__icon{right:0;left:auto}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{margin:.5em 0;font-weight:700;font-size:.64rem;line-height:1.6}.md-search-result__teaser{display:-webkit-box;max-height:2rem;margin:.5em 0;overflow:hidden;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}@media screen and (max-width:44.9375em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}.md-search-result__teaser mark{text-decoration:underline;background-color:initial}.md-search-result__terms{margin:.5em 0;font-size:.64rem;font-style:italic}.md-search-result mark{color:var(--md-accent-fg-color);background-color:initial}.md-select{position:relative;z-index:1}.md-select__inner{position:absolute;top:calc(100% - .2rem);left:50%;max-height:0;margin-top:.2rem;color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);transform:translate3d(-50%,.3rem,0);opacity:0;transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;transform:translate3d(-50%,0,0);opacity:1;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s}.md-select__inner:after{position:absolute;top:0;left:50%;width:0;height:0;margin-top:-.2rem;margin-left:-.2rem;border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);content:""}.md-select__list{max-height:inherit;margin:0;padding:0;overflow:auto;font-size:.8rem;list-style-type:none;border-radius:.1rem}.md-select__item{line-height:1.8rem}.md-select__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;cursor:pointer;transition:background-color .25s,color .25s;scroll-snap-align:start}[dir=rtl] .md-select__link{padding-right:.6rem;padding-left:1.2rem}.md-select__link:focus,.md-select__link:hover{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{position:-webkit-sticky;position:sticky;top:2.4rem;flex-shrink:0;align-self:flex-start;width:12.1rem;padding:1.2rem 0}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;z-index:3;display:block;width:12.1rem;height:100%;background-color:var(--md-default-bg-color);transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;-ms-scroll-snap-type:none;scroll-snap-type:none;overflow:hidden}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{position:fixed;top:0;z-index:3;width:0;height:0;background-color:rgba(0,0,0,.54);opacity:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}:root{--md-source-forks-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm0 2.122a2.25 2.25 0 1 0-1.5 0v.878A2.25 2.25 0 0 0 5.75 8.5h1.5v2.128a2.251 2.251 0 1 0 1.5 0V8.5h1.5a2.25 2.25 0 0 0 2.25-2.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75h-4.5A.75.75 0 0 1 5 6.25v-.878zm3.75 7.378a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm3-8.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5z'/></svg>");--md-source-repositories-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 1 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 0 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 0 1 1-1h8zM5 12.25v3.25a.25.25 0 0 0 .4.2l1.45-1.087a.25.25 0 0 1 .3 0L8.6 15.7a.25.25 0 0 0 .4-.2v-3.25a.25.25 0 0 0-.25-.25h-3.5a.25.25 0 0 0-.25.25z'/></svg>");--md-source-stars-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694v.001z'/></svg>");--md-source-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M2.5 7.775V2.75a.25.25 0 0 1 .25-.25h5.025a.25.25 0 0 1 .177.073l6.25 6.25a.25.25 0 0 1 0 .354l-5.025 5.025a.25.25 0 0 1-.354 0l-6.25-6.25a.25.25 0 0 1-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.75 1.75 0 0 1 1 7.775zM6 5a1 1 0 1 0 0 2 1 1 0 0 0 0-2z'/></svg>")}.md-source{display:block;font-size:.65rem;line-height:1.2;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity .25s}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;width:2rem;height:2.4rem;vertical-align:middle}.md-source__icon svg{margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);margin-left:.6rem;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{margin:.1rem 0 0;padding:0;overflow:hidden;font-size:.55rem;list-style-type:none;opacity:.75}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{display:inline-block}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{display:inline-block;width:.6rem;height:.6rem;margin-right:.1rem;vertical-align:text-top;background-color:currentColor;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-source__fact:nth-child(1n+2):before{margin-left:.4rem}[dir=rtl] .md-source__fact{margin-right:0;margin-left:.1rem}[dir=rtl] .md-source__fact:nth-child(1n+2):before{margin-right:.4rem;margin-left:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-tabs{width:100%;overflow:auto;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs__list{margin:0 0 0 .2rem;padding:0;white-space:nowrap;list-style:none;contain:content}[dir=rtl] .md-tabs__list{margin-right:.2rem;margin-left:0}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;font-size:.7rem;-webkit-backface-visibility:hidden;backface-visibility:hidden;opacity:.7;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden] .md-tabs__link{transform:translateY(50%);opacity:0;transition:transform 0ms .1s,opacity .1s}.md-top{position:-webkit-sticky;position:sticky;bottom:.4rem;z-index:1;float:right;margin:-2.8rem .4rem .4rem;padding:.4rem;color:var(--md-primary-bg-color);background:var(--md-primary-fg-color);border-radius:100%;outline:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transform:translateY(0);transition:opacity 125ms,transform 125ms cubic-bezier(.4,0,.2,1),background-color 125ms}[dir=rtl] .md-top{float:left}.md-top[data-md-state=hidden]{transform:translateY(-.2rem);opacity:0}.md-top:focus,.md-top:hover{background:var(--md-accent-fg-color);transform:scale(1.1)}:root{--md-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'><path d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/></svg>")}.md-version{flex-shrink:0;height:2.4rem;font-size:.8rem}.md-version__current{position:relative;top:.05rem;margin-right:.4rem;margin-left:1.4rem}[dir=rtl] .md-version__current{margin-right:1.4rem;margin-left:.4rem}.md-version__current:after{display:inline-block;width:.4rem;height:.6rem;margin-left:.4rem;background-color:currentColor;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;content:""}[dir=rtl] .md-version__current:after{margin-right:.4rem;margin-left:0}.md-version__list{position:absolute;top:.15rem;z-index:1;max-height:1.8rem;margin:.2rem .8rem;padding:0;overflow:auto;color:var(--md-default-fg-color);list-style-type:none;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);opacity:0;transition:max-height 0ms .5s,opacity .25s .25s;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory}.md-version__list:focus-within,.md-version__list:hover{max-height:10rem;opacity:1;transition:max-height .25s,opacity .25s}.md-version__item{line-height:1.8rem}.md-version__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;white-space:nowrap;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}[dir=rtl] .md-version__link{padding-right:.6rem;padding-left:1.2rem}.md-version__link:focus,.md-version__link:hover{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");--md-admonition-icon--abstract:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z'/></svg>");--md-admonition-icon--info:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");--md-admonition-icon--tip:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");--md-admonition-icon--success:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/></svg>");--md-admonition-icon--question:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");--md-admonition-icon--warning:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");--md-admonition-icon--failure:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z'/></svg>");--md-admonition-icon--danger:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m11.5 20 4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z'/></svg>");--md-admonition-icon--bug:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");--md-admonition-icon--example:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");--md-admonition-icon--quote:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>")}.md-typeset .admonition,.md-typeset details{margin:1.5625em 0;padding:0 .6rem;overflow:hidden;color:var(--md-admonition-fg-color);font-size:.64rem;page-break-inside:avoid;background-color:var(--md-admonition-bg-color);border-left:.2rem solid #448aff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 .025rem .05rem rgba(0,0,0,.05)}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-top:1em;margin-bottom:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition-title,.md-typeset summary{position:relative;margin:0 -.6rem 0 -.8rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(68,138,255,.1);border-left:.2rem solid #448aff}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{margin:0 -.8rem 0 -.6rem;padding:.4rem 2rem .4rem .6rem;border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}.md-typeset .admonition-title:before,.md-typeset summary:before{position:absolute;left:.6rem;width:1rem;height:1rem;background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem;left:auto}.md-typeset .admonition-title+.tabbed-set:last-child,.md-typeset summary+.tabbed-set:last-child{margin-top:0}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:rgba(68,138,255,.1);border-color:#448aff}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-color:#00b0ff}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary,.md-typeset .summary>.admonition-title,.md-typeset .summary>summary,.md-typeset .tldr>.admonition-title,.md-typeset .tldr>summary{background-color:rgba(0,176,255,.1);border-color:#00b0ff}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before,.md-typeset .summary>.admonition-title:before,.md-typeset .summary>summary:before,.md-typeset .tldr>.admonition-title:before,.md-typeset .tldr>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-color:#00b8d4}.md-typeset .info>.admonition-title,.md-typeset .info>summary,.md-typeset .todo>.admonition-title,.md-typeset .todo>summary{background-color:rgba(0,184,212,.1);border-color:#00b8d4}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before,.md-typeset .todo>.admonition-title:before,.md-typeset .todo>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .hint>.admonition-title,.md-typeset .hint>summary,.md-typeset .important>.admonition-title,.md-typeset .important>summary,.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:rgba(0,191,165,.1);border-color:#00bfa5}.md-typeset .hint>.admonition-title:before,.md-typeset .hint>summary:before,.md-typeset .important>.admonition-title:before,.md-typeset .important>summary:before,.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-color:#00c853}.md-typeset .check>.admonition-title,.md-typeset .check>summary,.md-typeset .done>.admonition-title,.md-typeset .done>summary,.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:rgba(0,200,83,.1);border-color:#00c853}.md-typeset .check>.admonition-title:before,.md-typeset .check>summary:before,.md-typeset .done>.admonition-title:before,.md-typeset .done>summary:before,.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-color:#64dd17}.md-typeset .faq>.admonition-title,.md-typeset .faq>summary,.md-typeset .help>.admonition-title,.md-typeset .help>summary,.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:rgba(100,221,23,.1);border-color:#64dd17}.md-typeset .faq>.admonition-title:before,.md-typeset .faq>summary:before,.md-typeset .help>.admonition-title:before,.md-typeset .help>summary:before,.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-color:#ff9100}.md-typeset .attention>.admonition-title,.md-typeset .attention>summary,.md-typeset .caution>.admonition-title,.md-typeset .caution>summary,.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:rgba(255,145,0,.1);border-color:#ff9100}.md-typeset .attention>.admonition-title:before,.md-typeset .attention>summary:before,.md-typeset .caution>.admonition-title:before,.md-typeset .caution>summary:before,.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-color:#ff5252}.md-typeset .fail>.admonition-title,.md-typeset .fail>summary,.md-typeset .failure>.admonition-title,.md-typeset .failure>summary,.md-typeset .missing>.admonition-title,.md-typeset .missing>summary{background-color:rgba(255,82,82,.1);border-color:#ff5252}.md-typeset .fail>.admonition-title:before,.md-typeset .fail>summary:before,.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before,.md-typeset .missing>.admonition-title:before,.md-typeset .missing>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-color:#ff1744}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary,.md-typeset .error>.admonition-title,.md-typeset .error>summary{background-color:rgba(255,23,68,.1);border-color:#ff1744}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before,.md-typeset .error>.admonition-title:before,.md-typeset .error>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:rgba(245,0,87,.1);border-color:#f50057}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:rgba(124,77,255,.1);border-color:#7c4dff}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .cite>.admonition-title,.md-typeset .cite>summary,.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:hsla(0,0%,62%,.1);border-color:#9e9e9e}.md-typeset .cite>.admonition-title:before,.md-typeset .cite>summary:before,.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z'/></svg>")}.md-typeset [id^="fnref:"]:target{scroll-margin-top:0;margin-top:-3.4rem;padding-top:3.4rem}.md-typeset [id^="fn:"]:target{scroll-margin-top:0;margin-top:-3.45rem;padding-top:3.45rem}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}.md-typeset .footnote>ol{margin-left:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{transform:translateX(0);opacity:1}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-backref{display:inline-block;color:var(--md-typeset-a-color);font-size:0;vertical-align:text-bottom;transform:translateX(.25rem);opacity:0;transition:color .25s,transform .25s .25s,opacity 125ms .25s}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);transform:translateX(0);opacity:1}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{display:inline-block;width:.8rem;height:.8rem;background-color:currentColor;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;color:var(--md-default-fg-color--lighter);opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{scroll-margin-top:3.6rem}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{scroll-margin-top:0}.md-typeset h1:target:before,.md-typeset h2:target:before,.md-typeset h3:target:before{display:block;margin-top:-3.4rem;padding-top:3.4rem;content:""}.md-typeset h4:target{scroll-margin-top:0}.md-typeset h4:target:before{display:block;margin-top:-3.45rem;padding-top:3.45rem;content:""}.md-typeset h5:target,.md-typeset h6:target{scroll-margin-top:0}.md-typeset h5:target:before,.md-typeset h6:target:before{display:block;margin-top:-3.6rem;padding-top:3.6rem;content:""}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{width:-webkit-min-content;width:-moz-min-content;width:min-content;margin:1em auto!important;padding:0 .8rem;touch-action:auto}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;overflow:auto;box-shadow:none}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>")}.md-typeset details{display:flow-root;padding-top:0;overflow:visible}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){padding-bottom:0;box-shadow:none}.md-typeset details:not([open])>summary{border-radius:.1rem}.md-typeset details:after{display:table;content:""}.md-typeset summary{display:block;min-height:1rem;padding:.4rem 1.8rem .4rem 2rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;cursor:pointer}[dir=rtl] .md-typeset summary{padding:.4rem 2.2rem .4rem 1.8rem}.md-typeset summary:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset summary:after{position:absolute;top:.4rem;right:.4rem;width:1rem;height:1rem;background-color:currentColor;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transform:rotate(0deg);transition:transform .25s;content:""}[dir=rtl] .md-typeset summary:after{right:auto;left:.4rem;transform:rotate(180deg)}.md-typeset summary::-webkit-details-marker,.md-typeset summary::marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{width:1.125em;max-height:100%;fill:currentColor}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{margin:0 -.125em;padding:0 .125em;border-radius:.1rem}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em;background-color:var(--md-code-hl-color)}.highlight [data-linenos]:before{position:-webkit-sticky;position:sticky;left:-1.1764705882em;float:left;margin-right:1.1764705882em;margin-left:-1.1764705882em;padding-left:1.1764705882em;color:var(--md-default-fg-color--light);background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable{display:flow-root;overflow:hidden}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable .linenos{padding:.7720588235em 0 .7720588235em 1.1764705882em;font-size:.85em;background-color:var(--md-code-bg-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{padding-right:.5882352941em;box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;overflow:hidden}.md-typeset .highlighttable{margin:1em 0;direction:ltr;border-radius:.1rem}.md-typeset .highlighttable code{border-radius:0}@media screen and (max-width:44.9375em){.md-typeset>.highlight{margin:1em -.8rem}.md-typeset>.highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.highlight code{border-radius:0}.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.highlighttable .hll{margin:0 -.8rem;padding:0 .8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{position:relative;margin:0;color:inherit;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.md-typeset .keys span{padding:0 .2em;color:var(--md-default-fg-color--light)}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{padding-right:.4em;content:"⎇"}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{padding-right:.4em;content:"⌘"}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{padding-right:.4em;content:"⌃"}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{padding-right:.4em;content:"◆"}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{padding-right:.4em;content:"⌥"}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{padding-right:.4em;content:"⇧"}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{padding-right:.4em;content:"❖"}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{padding-right:.4em;content:"⊞"}.md-typeset .keys .key-arrow-down:before{padding-right:.4em;content:"↓"}.md-typeset .keys .key-arrow-left:before{padding-right:.4em;content:"←"}.md-typeset .keys .key-arrow-right:before{padding-right:.4em;content:"→"}.md-typeset .keys .key-arrow-up:before{padding-right:.4em;content:"↑"}.md-typeset .keys .key-backspace:before{padding-right:.4em;content:"⌫"}.md-typeset .keys .key-backtab:before{padding-right:.4em;content:"⇤"}.md-typeset .keys .key-caps-lock:before{padding-right:.4em;content:"⇪"}.md-typeset .keys .key-clear:before{padding-right:.4em;content:"⌧"}.md-typeset .keys .key-context-menu:before{padding-right:.4em;content:"☰"}.md-typeset .keys .key-delete:before{padding-right:.4em;content:"⌦"}.md-typeset .keys .key-eject:before{padding-right:.4em;content:"⏏"}.md-typeset .keys .key-end:before{padding-right:.4em;content:"⤓"}.md-typeset .keys .key-escape:before{padding-right:.4em;content:"⎋"}.md-typeset .keys .key-home:before{padding-right:.4em;content:"⤒"}.md-typeset .keys .key-insert:before{padding-right:.4em;content:"⎀"}.md-typeset .keys .key-page-down:before{padding-right:.4em;content:"⇟"}.md-typeset .keys .key-page-up:before{padding-right:.4em;content:"⇞"}.md-typeset .keys .key-print-screen:before{padding-right:.4em;content:"⎙"}.md-typeset .keys .key-tab:after{padding-left:.4em;content:"⇥"}.md-typeset .keys .key-num-enter:after{padding-left:.4em;content:"⌤"}.md-typeset .keys .key-enter:after{padding-left:.4em;content:"⏎"}.md-typeset .tabbed-content{display:none;order:99;width:100%;box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}@media print{.md-typeset .tabbed-content{display:block;order:0}}.md-typeset .tabbed-content>.highlight:only-child pre,.md-typeset .tabbed-content>.highlighttable:only-child,.md-typeset .tabbed-content>pre:only-child{margin:0}.md-typeset .tabbed-content>.highlight:only-child pre>code,.md-typeset .tabbed-content>.highlighttable:only-child>code,.md-typeset .tabbed-content>pre:only-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-content>.tabbed-set{margin:0}.md-typeset .tabbed-set{position:relative;display:flex;flex-wrap:wrap;margin:1em 0;border-radius:.1rem}.md-typeset .tabbed-set>input{position:absolute;width:0;height:0;opacity:0}.md-typeset .tabbed-set>input:checked+label{color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:checked+label+.tabbed-content{display:block}.md-typeset .tabbed-set>input:focus+label{outline-style:auto}.md-typeset .tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.md-typeset .tabbed-set>label{z-index:1;width:auto;padding:.9375em 1.25em .78125em;color:var(--md-default-fg-color--light);font-weight:700;font-size:.64rem;border-bottom:.1rem solid transparent;cursor:pointer;transition:color .25s}.md-typeset .tabbed-set>label:hover{color:var(--md-accent-fg-color)}:root{--md-tasklist-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>");--md-tasklist-icon--checked:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>")}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control [type=checkbox]{z-index:-1;opacity:0}.md-typeset .task-list-indicator:before{position:absolute;top:.15em;left:-1.5em;width:1.25em;height:1.25em;background-color:var(--md-default-fg-color--lightest);-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em;left:auto}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media screen and (min-width:45em){.md-typeset .inline{float:left;width:11.7rem;margin-top:0;margin-right:.8rem;margin-bottom:.8rem}.md-typeset .inline.end,[dir=rtl] .md-typeset .inline{float:right;margin-right:0;margin-left:.8rem}[dir=rtl] .md-typeset .inline.end{float:left;margin-right:.8rem;margin-left:0}}
+/*# sourceMappingURL=main.33e2939f.min.css.map */
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/main.33e2939f.min.css.map b/5.4/assets/stylesheets/main.33e2939f.min.css.map
new file mode 100644 (file)
index 0000000..2b31341
--- /dev/null
@@ -0,0 +1 @@
+{"version":3,"sources":["src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_reset.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","node_modules/material-shadows/material-shadows.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_announce.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_top.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/main.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Keyboard key\n  .keys {\n\n    // Keyboard key icon\n    kbd::before,\n    kbd::after {\n      position: relative;\n      margin: 0;\n      color: inherit;\n      -moz-osx-font-smoothing: initial;\n      -webkit-font-smoothing: initial;\n    }\n\n    // Surrounding text\n    span {\n      padding: 0 px2em(3.2px);\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Define keyboard keys with left icon\n    @each $name, $code in (\n\n      // Modifiers\n      \"alt\":           \"\\2387\",\n      \"left-alt\":      \"\\2387\",\n      \"right-alt\":     \"\\2387\",\n      \"command\":       \"\\2318\",\n      \"left-command\":  \"\\2318\",\n      \"right-command\": \"\\2318\",\n      \"control\":       \"\\2303\",\n      \"left-control\":  \"\\2303\",\n      \"right-control\": \"\\2303\",\n      \"meta\":          \"\\25C6\",\n      \"left-meta\":     \"\\25C6\",\n      \"right-meta\":    \"\\25C6\",\n      \"option\":        \"\\2325\",\n      \"left-option\":   \"\\2325\",\n      \"right-option\":  \"\\2325\",\n      \"shift\":         \"\\21E7\",\n      \"left-shift\":    \"\\21E7\",\n      \"right-shift\":   \"\\21E7\",\n      \"super\":         \"\\2756\",\n      \"left-super\":    \"\\2756\",\n      \"right-super\":   \"\\2756\",\n      \"windows\":       \"\\229E\",\n      \"left-windows\":  \"\\229E\",\n      \"right-windows\": \"\\229E\",\n\n      // Other keys\n      \"arrow-down\":    \"\\2193\",\n      \"arrow-left\":    \"\\2190\",\n      \"arrow-right\":   \"\\2192\",\n      \"arrow-up\":      \"\\2191\",\n      \"backspace\":     \"\\232B\",\n      \"backtab\":       \"\\21E4\",\n      \"caps-lock\":     \"\\21EA\",\n      \"clear\":         \"\\2327\",\n      \"context-menu\":  \"\\2630\",\n      \"delete\":        \"\\2326\",\n      \"eject\":         \"\\23CF\",\n      \"end\":           \"\\2913\",\n      \"escape\":        \"\\238B\",\n      \"home\":          \"\\2912\",\n      \"insert\":        \"\\2380\",\n      \"page-down\":     \"\\21DF\",\n      \"page-up\":       \"\\21DE\",\n      \"print-screen\":  \"\\2399\"\n    ) {\n      .key-#{$name} {\n        &::before {\n          padding-right: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n\n    // Define keyboard keys with right icon\n    @each $name, $code in (\n      \"tab\":           \"\\21E5\",\n      \"num-enter\":     \"\\2324\",\n      \"enter\":         \"\\23CE\"\n    ) {\n      .key-#{$name} {\n        &::after {\n          padding-left: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n  }\n}\n","@charset \"UTF-8\";\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n}\n\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\na {\n  color: inherit;\n  text-decoration: none;\n}\n\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: 0.05rem;\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  line-height: 1em;\n}\n\nimg {\n  border-style: none;\n}\n\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\ninput {\n  border: 0;\n  outline: none;\n}\n\n:root {\n  --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color: hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n:root > * {\n  --md-code-fg-color: hsla(200, 18%, 26%, 1);\n  --md-code-bg-color: hsla(0, 0%, 96%, 1);\n  --md-code-hl-color: hsla(60, 100%, 50%, 0.5);\n  --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n  --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n  --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n  --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n  --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n  --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n  --md-code-hl-name-color: var(--md-code-fg-color);\n  --md-code-hl-operator-color: var(--md-default-fg-color--light);\n  --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n  --md-code-hl-comment-color: var(--md-default-fg-color--light);\n  --md-code-hl-generic-color: var(--md-default-fg-color--light);\n  --md-code-hl-variable-color: var(--md-default-fg-color--light);\n  --md-typeset-color: var(--md-default-fg-color);\n  --md-typeset-a-color: var(--md-primary-fg-color);\n  --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5);\n  --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n  --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n  --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n  --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n  --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n  --md-admonition-fg-color: var(--md-default-fg-color);\n  --md-admonition-bg-color: var(--md-default-bg-color);\n  --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n  --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n}\n\n.md-icon svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family: var(--md-text-font-family, _), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family: var(--md-code-font-family, _), SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n.md-typeset {\n  font-size: 0.8rem;\n  line-height: 1.6;\n  color-adjust: exact;\n}\n@media print {\n  .md-typeset {\n    font-size: 0.68rem;\n  }\n}\n.md-typeset ul,\n.md-typeset ol,\n.md-typeset dl,\n.md-typeset figure,\n.md-typeset blockquote,\n.md-typeset pre {\n  display: flow-root;\n  margin: 1em 0;\n}\n.md-typeset h1 {\n  margin: 0 0 1.25em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 300;\n  font-size: 2em;\n  line-height: 1.3;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 {\n  margin: 1.6em 0 0.64em;\n  font-weight: 300;\n  font-size: 1.5625em;\n  line-height: 1.4;\n  letter-spacing: -0.01em;\n}\n.md-typeset h3 {\n  margin: 1.6em 0 0.8em;\n  font-weight: 400;\n  font-size: 1.25em;\n  line-height: 1.5;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 + h3 {\n  margin-top: 0.8em;\n}\n.md-typeset h4 {\n  margin: 1em 0;\n  font-weight: 700;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5,\n.md-typeset h6 {\n  margin: 1.25em 0;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.8em;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5 {\n  text-transform: uppercase;\n}\n.md-typeset hr {\n  display: flow-root;\n  margin: 1.5em 0;\n  border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset a {\n  color: var(--md-typeset-a-color);\n  word-break: break-word;\n}\n.md-typeset a, .md-typeset a::before {\n  transition: color 125ms;\n}\n.md-typeset a:focus, .md-typeset a:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n  color: var(--md-code-fg-color);\n  direction: ltr;\n}\n@media print {\n  .md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n    white-space: pre-wrap;\n  }\n}\n.md-typeset code {\n  padding: 0 0.2941176471em;\n  font-size: 0.85em;\n  word-break: break-word;\n  background-color: var(--md-code-bg-color);\n  border-radius: 0.1rem;\n  box-decoration-break: clone;\n}\n.md-typeset code:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset h1 code,\n.md-typeset h2 code,\n.md-typeset h3 code,\n.md-typeset h4 code,\n.md-typeset h5 code,\n.md-typeset h6 code {\n  margin: initial;\n  padding: initial;\n  background-color: transparent;\n  box-shadow: none;\n}\n.md-typeset a code {\n  color: currentColor;\n}\n.md-typeset pre {\n  position: relative;\n  line-height: 1.4;\n}\n.md-typeset pre > code {\n  display: block;\n  margin: 0;\n  padding: 0.7720588235em 1.1764705882em;\n  overflow: auto;\n  word-break: normal;\n  box-shadow: none;\n  box-decoration-break: slice;\n  touch-action: auto;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-typeset pre > code:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-typeset pre > code::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > pre {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > pre code {\n    border-radius: 0;\n  }\n}\n.md-typeset kbd {\n  display: inline-block;\n  padding: 0 0.6666666667em;\n  color: var(--md-default-fg-color);\n  font-size: 0.75em;\n  vertical-align: text-top;\n  word-break: break-word;\n  background-color: var(--md-typeset-kbd-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.1rem 0 0.05rem var(--md-typeset-kbd-border-color), 0 0.1rem 0 var(--md-typeset-kbd-border-color), 0 -0.1rem 0.2rem var(--md-typeset-kbd-accent-color) inset;\n}\n.md-typeset mark {\n  color: inherit;\n  word-break: break-word;\n  background-color: var(--md-typeset-mark-color);\n  box-decoration-break: clone;\n}\n.md-typeset abbr {\n  text-decoration: none;\n  border-bottom: 0.05rem dotted var(--md-default-fg-color--light);\n  cursor: help;\n}\n@media (hover: none) {\n  .md-typeset abbr {\n    position: relative;\n  }\n  .md-typeset abbr[title]:focus::after, .md-typeset abbr[title]:hover::after {\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n    position: absolute;\n    left: 0;\n    display: inline-block;\n    width: auto;\n    min-width: max-content;\n    max-width: 80%;\n    margin-top: 2em;\n    padding: 0.2rem 0.3rem;\n    color: var(--md-default-bg-color);\n    font-size: 0.7rem;\n    background-color: var(--md-default-fg-color);\n    border-radius: 0.1rem;\n    content: attr(title);\n  }\n}\n.md-typeset small {\n  opacity: 0.75;\n}\n.md-typeset sup,\n.md-typeset sub {\n  margin-left: 0.078125em;\n}\n[dir=rtl] .md-typeset sup,\n[dir=rtl] .md-typeset sub {\n  margin-right: 0.078125em;\n  margin-left: initial;\n}\n.md-typeset blockquote {\n  padding-left: 0.6rem;\n  color: var(--md-default-fg-color--light);\n  border-left: 0.2rem solid var(--md-default-fg-color--lighter);\n}\n[dir=rtl] .md-typeset blockquote {\n  padding-right: 0.6rem;\n  padding-left: initial;\n  border-right: 0.2rem solid var(--md-default-fg-color--lighter);\n  border-left: initial;\n}\n.md-typeset ul {\n  list-style-type: disc;\n}\n.md-typeset ul,\n.md-typeset ol {\n  margin-left: 0.625em;\n  padding: 0;\n}\n[dir=rtl] .md-typeset ul,\n[dir=rtl] .md-typeset ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset ul ol,\n.md-typeset ol ol {\n  list-style-type: lower-alpha;\n}\n.md-typeset ul ol ol,\n.md-typeset ol ol ol {\n  list-style-type: lower-roman;\n}\n.md-typeset ul li,\n.md-typeset ol li {\n  margin-bottom: 0.5em;\n  margin-left: 1.25em;\n}\n[dir=rtl] .md-typeset ul li,\n[dir=rtl] .md-typeset ol li {\n  margin-right: 1.25em;\n  margin-left: initial;\n}\n.md-typeset ul li p,\n.md-typeset ul li blockquote,\n.md-typeset ol li p,\n.md-typeset ol li blockquote {\n  margin: 0.5em 0;\n}\n.md-typeset ul li:last-child,\n.md-typeset ol li:last-child {\n  margin-bottom: 0;\n}\n.md-typeset ul li ul,\n.md-typeset ul li ol,\n.md-typeset ol li ul,\n.md-typeset ol li ol {\n  margin: 0.5em 0 0.5em 0.625em;\n}\n[dir=rtl] .md-typeset ul li ul,\n[dir=rtl] .md-typeset ul li ol,\n[dir=rtl] .md-typeset ol li ul,\n[dir=rtl] .md-typeset ol li ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset dd {\n  margin: 1em 0 1.5em 1.875em;\n}\n[dir=rtl] .md-typeset dd {\n  margin-right: 1.875em;\n  margin-left: initial;\n}\n.md-typeset img,\n.md-typeset svg {\n  max-width: 100%;\n  height: auto;\n}\n.md-typeset img[align=left],\n.md-typeset svg[align=left] {\n  margin: 1em;\n  margin-left: 0;\n}\n.md-typeset img[align=right],\n.md-typeset svg[align=right] {\n  margin: 1em;\n  margin-right: 0;\n}\n.md-typeset img[align]:only-child,\n.md-typeset svg[align]:only-child {\n  margin-top: 0;\n}\n.md-typeset figure {\n  width: fit-content;\n  max-width: 100%;\n  margin: 0 auto;\n  text-align: center;\n}\n.md-typeset figure img {\n  display: block;\n}\n.md-typeset figcaption {\n  max-width: 24rem;\n  margin: 1em auto 2em;\n  font-style: italic;\n}\n.md-typeset iframe {\n  max-width: 100%;\n}\n.md-typeset table:not([class]) {\n  display: inline-block;\n  max-width: 100%;\n  overflow: auto;\n  font-size: 0.64rem;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);\n  touch-action: auto;\n}\n@media print {\n  .md-typeset table:not([class]) {\n    display: table;\n  }\n}\n.md-typeset table:not([class]) + * {\n  margin-top: 1.5em;\n}\n.md-typeset table:not([class]) th > *:first-child,\n.md-typeset table:not([class]) td > *:first-child {\n  margin-top: 0;\n}\n.md-typeset table:not([class]) th > *:last-child,\n.md-typeset table:not([class]) td > *:last-child {\n  margin-bottom: 0;\n}\n.md-typeset table:not([class]) th:not([align]),\n.md-typeset table:not([class]) td:not([align]) {\n  text-align: left;\n}\n[dir=rtl] .md-typeset table:not([class]) th:not([align]),\n[dir=rtl] .md-typeset table:not([class]) td:not([align]) {\n  text-align: right;\n}\n.md-typeset table:not([class]) th {\n  min-width: 5rem;\n  padding: 0.9375em 1.25em;\n  color: var(--md-default-bg-color);\n  vertical-align: top;\n  background-color: var(--md-default-fg-color--light);\n}\n.md-typeset table:not([class]) th a {\n  color: inherit;\n}\n.md-typeset table:not([class]) td {\n  padding: 0.9375em 1.25em;\n  vertical-align: top;\n  border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset table:not([class]) tr {\n  transition: background-color 125ms;\n}\n.md-typeset table:not([class]) tr:hover {\n  background-color: rgba(0, 0, 0, 0.035);\n  box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;\n}\n.md-typeset table:not([class]) tr:first-child td {\n  border-top: 0;\n}\n.md-typeset table:not([class]) a {\n  word-break: normal;\n}\n.md-typeset table th[role=columnheader] {\n  cursor: pointer;\n}\n.md-typeset table th[role=columnheader]::after {\n  display: inline-block;\n  width: 1.2em;\n  height: 1.2em;\n  margin-left: 0.5em;\n  vertical-align: sub;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-typeset table th[role=columnheader][aria-sort=ascending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--ascending);\n}\n.md-typeset table th[role=columnheader][aria-sort=descending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--descending);\n}\n.md-typeset__scrollwrap {\n  margin: 1em -0.8rem;\n  overflow-x: auto;\n  touch-action: auto;\n}\n.md-typeset__table {\n  display: inline-block;\n  margin-bottom: 0.5em;\n  padding: 0 0.8rem;\n}\n@media print {\n  .md-typeset__table {\n    display: block;\n  }\n}\nhtml .md-typeset__table table {\n  display: table;\n  width: 100%;\n  margin: 0;\n  overflow: hidden;\n}\n\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  font-size: 125%;\n}\n@media screen and (min-width: 100em) {\n  html {\n    font-size: 137.5%;\n  }\n}\n@media screen and (min-width: 125em) {\n  html {\n    font-size: 150%;\n  }\n}\n\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  font-size: 0.5rem;\n  background-color: var(--md-default-bg-color);\n}\n@media print {\n  body {\n    display: block;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  body[data-md-state=lock] {\n    position: fixed;\n  }\n}\n\n.md-grid {\n  max-width: 61rem;\n  margin-right: auto;\n  margin-left: auto;\n}\n\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n}\n@media print {\n  .md-container {\n    display: block;\n  }\n}\n\n.md-main {\n  flex-grow: 1;\n}\n.md-main__inner {\n  display: flex;\n  height: 100%;\n  margin-top: 1.5rem;\n}\n\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.md-toggle {\n  display: none;\n}\n\n.md-option {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-option:checked + label:not([hidden]) {\n  display: block;\n}\n.md-option.focus-visible + label {\n  outline-style: auto;\n}\n\n.md-skip {\n  position: fixed;\n  z-index: -1;\n  margin: 0.5rem;\n  padding: 0.3rem 0.5rem;\n  color: var(--md-default-bg-color);\n  font-size: 0.64rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(0.4rem);\n  opacity: 0;\n}\n.md-skip:focus {\n  z-index: 10;\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 175ms 75ms;\n}\n\n@page {\n  margin: 25mm;\n}\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-announce {\n    display: none;\n  }\n}\n.md-announce__inner {\n  margin: 0.6rem auto;\n  padding: 0 0.8rem;\n  color: var(--md-footer-fg-color);\n  font-size: 0.7rem;\n}\n\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n.md-clipboard {\n  position: absolute;\n  top: 0.5em;\n  right: 0.5em;\n  z-index: 1;\n  width: 1.5em;\n  height: 1.5em;\n  color: var(--md-default-fg-color--lightest);\n  border-radius: 0.1rem;\n  cursor: pointer;\n  transition: color 250ms;\n}\n@media print {\n  .md-clipboard {\n    display: none;\n  }\n}\n.md-clipboard:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n:hover > .md-clipboard {\n  color: var(--md-default-fg-color--light);\n}\n.md-clipboard:focus, .md-clipboard:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-clipboard::after {\n  display: block;\n  width: 1.125em;\n  height: 1.125em;\n  margin: 0 auto;\n  background-color: currentColor;\n  mask-image: var(--md-clipboard-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-clipboard--inline {\n  cursor: pointer;\n}\n.md-clipboard--inline code {\n  transition: color 250ms, background-color 250ms;\n}\n.md-clipboard--inline:focus code, .md-clipboard--inline:hover code {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n\n.md-content {\n  flex-grow: 1;\n  overflow: hidden;\n  scroll-padding-top: 51.2rem;\n}\n.md-content__inner {\n  margin: 0 0.8rem 1.2rem;\n  padding-top: 0.6rem;\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-left: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n    margin-left: 0.8rem;\n  }\n  .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 0.8rem;\n    margin-left: 1.2rem;\n  }\n}\n.md-content__inner::before {\n  display: block;\n  height: 0.4rem;\n  content: \"\";\n}\n.md-content__inner > :last-child {\n  margin-bottom: 0;\n}\n.md-content__button {\n  float: right;\n  margin: 0.4rem 0;\n  margin-left: 0.4rem;\n  padding: 0;\n}\n@media print {\n  .md-content__button {\n    display: none;\n  }\n}\n[dir=rtl] .md-content__button {\n  float: left;\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n[dir=rtl] .md-content__button svg {\n  transform: scaleX(-1);\n}\n.md-typeset .md-content__button {\n  color: var(--md-default-fg-color--lighter);\n}\n.md-content__button svg {\n  display: inline;\n  vertical-align: top;\n}\n\n.md-dialog {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n  position: fixed;\n  right: 0.8rem;\n  bottom: 0.8rem;\n  left: initial;\n  z-index: 2;\n  min-width: 11.1rem;\n  padding: 0.4rem 0.6rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(100%);\n  opacity: 0;\n  transition: transform 0ms 400ms, opacity 400ms;\n  pointer-events: none;\n}\n@media print {\n  .md-dialog {\n    display: none;\n  }\n}\n[dir=rtl] .md-dialog {\n  right: initial;\n  left: 0.8rem;\n}\n.md-dialog[data-md-state=open] {\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1), opacity 400ms;\n  pointer-events: initial;\n}\n.md-dialog__inner {\n  color: var(--md-default-bg-color);\n  font-size: 0.7rem;\n}\n\n.md-typeset .md-button {\n  display: inline-block;\n  padding: 0.625em 2em;\n  color: var(--md-primary-fg-color);\n  font-weight: 700;\n  border: 0.1rem solid currentColor;\n  border-radius: 0.1rem;\n  transition: color 125ms, background-color 125ms, border-color 125ms;\n}\n.md-typeset .md-button--primary {\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  border-color: var(--md-primary-fg-color);\n}\n.md-typeset .md-button:focus, .md-typeset .md-button:hover {\n  color: var(--md-accent-bg-color);\n  background-color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .md-input {\n  height: 1.8rem;\n  padding: 0 0.6rem;\n  font-size: 0.8rem;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transition: box-shadow 250ms;\n}\n.md-typeset .md-input:focus, .md-typeset .md-input:hover {\n  box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.15), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.15);\n}\n.md-typeset .md-input--stretch {\n  width: 100%;\n}\n\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0), 0 0.2rem 0.4rem rgba(0, 0, 0, 0);\n}\n@media print {\n  .md-header {\n    display: none;\n  }\n}\n.md-header[data-md-state=shadow] {\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), box-shadow 250ms;\n}\n.md-header[data-md-state=hidden] {\n  transform: translateY(-100%);\n  transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), box-shadow 250ms;\n}\n.md-header .focus-visible {\n  outline-color: currentColor;\n}\n.md-header__inner {\n  display: flex;\n  align-items: center;\n  padding: 0 0.2rem;\n}\n.md-header__button {\n  position: relative;\n  z-index: 1;\n  margin: 0.2rem;\n  padding: 0.4rem;\n  color: currentColor;\n  vertical-align: middle;\n  cursor: pointer;\n  transition: opacity 250ms;\n}\n.md-header__button:hover {\n  opacity: 0.7;\n}\n.md-header__button:not([hidden]) {\n  display: inline-block;\n}\n.md-header__button:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-header__button.md-logo {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-header__button.md-logo {\n    display: none;\n  }\n}\n.md-header__button.md-logo img,\n.md-header__button.md-logo svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n@media screen and (min-width: 60em) {\n  .md-header__button[for=__search] {\n    display: none;\n  }\n}\n.no-js .md-header__button[for=__search] {\n  display: none;\n}\n[dir=rtl] .md-header__button[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__button[for=__drawer] {\n    display: none;\n  }\n}\n.md-header__topic {\n  position: absolute;\n  display: flex;\n  max-width: 100%;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n}\n.md-header__topic + .md-header__topic {\n  z-index: -1;\n  transform: translateX(1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__topic + .md-header__topic {\n  transform: translateX(-1.25rem);\n}\n.md-header__title {\n  flex-grow: 1;\n  height: 2.4rem;\n  margin-right: 0.4rem;\n  margin-left: 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-header__title[data-md-state=active] .md-header__topic {\n  z-index: -1;\n  transform: translateX(-1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic {\n  transform: translateX(1.25rem);\n}\n.md-header__title[data-md-state=active] .md-header__topic + .md-header__topic {\n  z-index: 0;\n  transform: translateX(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: initial;\n}\n.md-header__title > .md-header__ellipsis {\n  position: relative;\n  width: 100%;\n  height: 100%;\n}\n.md-header__option {\n  display: flex;\n  flex-shrink: 0;\n  max-width: 100%;\n  white-space: nowrap;\n  transition: max-width 0ms 250ms, opacity 250ms 250ms;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-header__option {\n  max-width: 0;\n  opacity: 0;\n  transition: max-width 0ms, opacity 0ms;\n}\n.md-header__source {\n  display: none;\n}\n@media screen and (min-width: 60em) {\n  .md-header__source {\n    display: block;\n    width: 11.7rem;\n    max-width: 11.7rem;\n    margin-left: 1rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1rem;\n    margin-left: initial;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__source {\n    margin-left: 1.4rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1.4rem;\n  }\n}\n\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-footer {\n    display: none;\n  }\n}\n.md-footer__inner {\n  padding: 0.2rem;\n  overflow: auto;\n}\n.md-footer__link {\n  display: flex;\n  padding-top: 1.4rem;\n  padding-bottom: 0.4rem;\n  transition: opacity 250ms;\n}\n@media screen and (min-width: 45em) {\n  .md-footer__link {\n    width: 50%;\n  }\n}\n.md-footer__link:focus, .md-footer__link:hover {\n  opacity: 0.7;\n}\n.md-footer__link--prev {\n  float: left;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--prev {\n    width: 25%;\n  }\n  .md-footer__link--prev .md-footer__title {\n    display: none;\n  }\n}\n[dir=rtl] .md-footer__link--prev {\n  float: right;\n}\n[dir=rtl] .md-footer__link--prev svg {\n  transform: scaleX(-1);\n}\n.md-footer__link--next {\n  float: right;\n  text-align: right;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--next {\n    width: 75%;\n  }\n}\n[dir=rtl] .md-footer__link--next {\n  float: left;\n  text-align: left;\n}\n[dir=rtl] .md-footer__link--next svg {\n  transform: scaleX(-1);\n}\n.md-footer__title {\n  position: relative;\n  flex-grow: 1;\n  max-width: calc(100% - 2.4rem);\n  padding: 0 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-footer__button {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n.md-footer__direction {\n  position: absolute;\n  right: 0;\n  left: 0;\n  margin-top: -1rem;\n  padding: 0 1rem;\n  font-size: 0.64rem;\n  opacity: 0.7;\n}\n\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n}\n.md-footer-meta__inner {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  padding: 0.2rem;\n}\nhtml .md-footer-meta.md-typeset a {\n  color: var(--md-footer-fg-color--light);\n}\nhtml .md-footer-meta.md-typeset a:focus, html .md-footer-meta.md-typeset a:hover {\n  color: var(--md-footer-fg-color);\n}\n\n.md-footer-copyright {\n  width: 100%;\n  margin: auto 0.6rem;\n  padding: 0.4rem 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: 0.64rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-copyright {\n    width: auto;\n  }\n}\n.md-footer-copyright__highlight {\n  color: var(--md-footer-fg-color--light);\n}\n\n.md-footer-social {\n  margin: 0 0.4rem;\n  padding: 0.2rem 0 0.6rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-social {\n    padding: 0.6rem 0;\n  }\n}\n.md-footer-social__link {\n  display: inline-block;\n  width: 1.6rem;\n  height: 1.6rem;\n  text-align: center;\n}\n.md-footer-social__link::before {\n  line-height: 1.9;\n}\n.md-footer-social__link svg {\n  max-height: 0.8rem;\n  vertical-align: -25%;\n  fill: currentColor;\n}\n\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n.md-nav {\n  font-size: 0.7rem;\n  line-height: 1.3;\n}\n.md-nav__title {\n  display: block;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n}\n.md-nav__title .md-nav__button {\n  display: none;\n}\n.md-nav__title .md-nav__button img {\n  width: auto;\n  height: 100%;\n}\n.md-nav__title .md-nav__button.md-logo img,\n.md-nav__title .md-nav__button.md-logo svg {\n  display: block;\n  width: 2.4rem;\n  height: 2.4rem;\n  fill: currentColor;\n}\n.md-nav__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-nav__item {\n  padding: 0 0.6rem;\n}\n.md-nav__item .md-nav__item {\n  padding-right: 0;\n}\n[dir=rtl] .md-nav__item .md-nav__item {\n  padding-right: 0.6rem;\n  padding-left: 0;\n}\n.md-nav__link {\n  display: block;\n  margin-top: 0.625em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  cursor: pointer;\n  transition: color 125ms;\n  scroll-snap-align: start;\n}\n.md-nav__link[data-md-state=blur] {\n  color: var(--md-default-fg-color--light);\n}\n.md-nav__item .md-nav__link--active {\n  color: var(--md-typeset-a-color);\n}\n.md-nav__item--nested > .md-nav__link {\n  color: inherit;\n}\n.md-nav__link:focus, .md-nav__link:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-nav--primary .md-nav__link[for=__toc] {\n  display: none;\n}\n.md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n  display: block;\n  width: 100%;\n  height: 100%;\n  mask-image: var(--md-toc-icon);\n  background-color: currentColor;\n}\n.md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n  display: none;\n}\n.md-nav__source {\n  display: none;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-nav--primary, .md-nav--primary .md-nav {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: 1;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n  }\n  .md-nav--primary .md-nav__title,\n.md-nav--primary .md-nav__item {\n    font-size: 0.8rem;\n    line-height: 1.5;\n  }\n  .md-nav--primary .md-nav__title {\n    position: relative;\n    height: 5.6rem;\n    padding: 3rem 0.8rem 0.2rem;\n    color: var(--md-default-fg-color--light);\n    font-weight: 400;\n    line-height: 2.4rem;\n    white-space: nowrap;\n    background-color: var(--md-default-fg-color--lightest);\n    cursor: pointer;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    position: absolute;\n    top: 0.4rem;\n    left: 0.4rem;\n    display: block;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin: 0.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon {\n    right: 0.4rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--prev);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list {\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0.05rem 0 var(--md-default-fg-color--lightest) inset;\n    scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child {\n    border-top: 0;\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color);\n  }\n  .md-nav--primary .md-nav__title .md-logo {\n    position: absolute;\n    top: 0.2rem;\n    left: 0.2rem;\n    display: block;\n    margin: 0.2rem;\n    padding: 0.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-logo {\n    right: 0.2rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__list {\n    flex: 1;\n  }\n  .md-nav--primary .md-nav__item {\n    padding: 0;\n    border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n  }\n  .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link {\n    color: var(--md-typeset-a-color);\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link:focus, .md-nav--primary .md-nav__item--active > .md-nav__link:hover {\n    color: var(--md-accent-fg-color);\n  }\n  .md-nav--primary .md-nav__link {\n    position: relative;\n    margin-top: 0;\n    padding: 0.6rem 0.8rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon {\n    position: absolute;\n    top: 50%;\n    right: 0.6rem;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin-top: -0.6rem;\n    color: inherit;\n    font-size: 1.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon {\n    right: initial;\n    left: 0.6rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  [dir=rtl] .md-nav--primary .md-nav__icon::after {\n    transform: scale(-1);\n  }\n  .md-nav--primary .md-nav--secondary .md-nav__link {\n    position: static;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav {\n    position: static;\n    background-color: transparent;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-left: 1.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-right: 1.4rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-left: 2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-right: 2rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 2.6rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 2.6rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 3.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 3.2rem;\n    padding-left: initial;\n  }\n  .md-nav--secondary {\n    background-color: transparent;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: flex;\n    transform: translateX(100%);\n    opacity: 0;\n    transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), opacity 125ms 50ms;\n  }\n  [dir=rtl] .md-nav__toggle ~ .md-nav {\n    transform: translateX(-100%);\n  }\n  .md-nav__toggle:checked ~ .md-nav {\n    transform: translateX(0);\n    opacity: 1;\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 125ms 125ms;\n  }\n  .md-nav__toggle:checked ~ .md-nav > .md-nav__list {\n    backface-visibility: hidden;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  .md-nav--primary .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n  .md-nav__source {\n    display: block;\n    padding: 0 0.2rem;\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color--dark);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-nav--integrated .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n    scroll-snap-align: initial;\n  }\n  [dir=rtl] .md-nav--integrated .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-nav--secondary .md-nav__title[for=__toc] {\n    scroll-snap-align: start;\n  }\n  .md-nav--secondary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-nav {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    scroll-snap-align: start;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: none;\n  }\n  .md-nav__toggle:checked ~ .md-nav, .md-nav__toggle:indeterminate ~ .md-nav {\n    display: block;\n  }\n  .md-nav__item--nested > .md-nav > .md-nav__title {\n    display: none;\n  }\n  .md-nav__item--section {\n    display: block;\n    margin: 1.25em 0;\n  }\n  .md-nav__item--section:last-child {\n    margin-bottom: 0;\n  }\n  .md-nav__item--section > .md-nav__link {\n    display: none;\n  }\n  .md-nav__item--section > .md-nav {\n    display: block;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__list > .md-nav__item {\n    padding: 0;\n  }\n  .md-nav__icon {\n    float: right;\n    width: 0.9rem;\n    height: 0.9rem;\n    transition: transform 250ms;\n  }\n  [dir=rtl] .md-nav__icon {\n    float: left;\n    transform: rotate(180deg);\n  }\n  .md-nav__icon::after {\n    display: inline-block;\n    width: 100%;\n    height: 100%;\n    vertical-align: -0.1rem;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon, .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link .md-nav__icon {\n    transform: rotate(90deg);\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--nested,\n.md-nav--lifted > .md-nav__title {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active {\n    display: block;\n    padding: 0;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0 0.6rem;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav__item {\n    padding-right: 0.6rem;\n  }\n  .md-nav--lifted .md-nav[data-md-level=\"1\"] {\n    display: block;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: block;\n    margin-bottom: 1.25em;\n    border-left: 0.05rem solid var(--md-primary-fg-color);\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav > .md-nav__title {\n    display: none;\n  }\n}\n\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n.md-search {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search {\n    padding: 0.2rem 0;\n  }\n}\n.no-js .md-search {\n  display: none;\n}\n.md-search__overlay {\n  z-index: 1;\n  opacity: 0;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__overlay {\n    position: absolute;\n    top: 0.2rem;\n    left: -2.2rem;\n    width: 2rem;\n    height: 2rem;\n    overflow: hidden;\n    background-color: var(--md-default-bg-color);\n    border-radius: 1rem;\n    transform-origin: center;\n    transition: transform 300ms 100ms, opacity 200ms 200ms;\n    pointer-events: none;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: -2.2rem;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    opacity: 1;\n    transition: transform 400ms, opacity 100ms;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    cursor: pointer;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: 0;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    width: 100%;\n    height: 200vh;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@media screen and (max-width: 29.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(45);\n  }\n}\n@media screen and (min-width: 30em) and (max-width: 44.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(60);\n  }\n}\n@media screen and (min-width: 45em) and (max-width: 59.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(75);\n  }\n}\n.md-search__inner {\n  backface-visibility: hidden;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__inner {\n    position: fixed;\n    top: 0;\n    left: 100%;\n    z-index: 2;\n    width: 100%;\n    height: 100%;\n    transform: translateX(5%);\n    opacity: 0;\n    transition: right 0ms 300ms, left 0ms 300ms, transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms 150ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    left: 0;\n    transform: translateX(0);\n    opacity: 1;\n    transition: right 0ms 0ms, left 0ms 0ms, transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms 150ms;\n  }\n  [dir=rtl] [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    right: 0;\n    left: initial;\n  }\n  html [dir=rtl] .md-search__inner {\n    right: 100%;\n    left: initial;\n    transform: translateX(-5%);\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__inner {\n    position: relative;\n    float: right;\n    width: 11.7rem;\n    padding: 0.1rem 0;\n    transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n  }\n  [dir=rtl] .md-search__inner {\n    float: left;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 34.4rem;\n  }\n}\n.md-search__form {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search__form {\n    border-radius: 0.1rem;\n  }\n}\n.md-search__input {\n  position: relative;\n  z-index: 2;\n  padding: 0 2.2rem 0 3.6rem;\n  text-overflow: ellipsis;\n  background-color: var(--md-default-bg-color);\n  box-shadow: 0 0 0.6rem transparent;\n  transition: color 250ms, background-color 250ms, box-shadow 250ms;\n}\n[dir=rtl] .md-search__input {\n  padding: 0 3.6rem 0 2.2rem;\n}\n.md-search__input::placeholder {\n  transition: color 250ms;\n}\n.md-search__input ~ .md-search__icon, .md-search__input::placeholder {\n  color: var(--md-default-fg-color--light);\n}\n.md-search__input::-ms-clear {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input {\n  box-shadow: 0 0 0.6rem rgba(0, 0, 0, 0.07);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__input {\n    width: 100%;\n    height: 2.4rem;\n    font-size: 0.9rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__input {\n    width: 100%;\n    height: 1.8rem;\n    padding-left: 2.2rem;\n    color: inherit;\n    font-size: 0.8rem;\n    background-color: rgba(0, 0, 0, 0.26);\n    border-radius: 0.1rem;\n  }\n  [dir=rtl] .md-search__input {\n    padding-right: 2.2rem;\n  }\n  .md-search__input + .md-search__icon {\n    color: var(--md-primary-bg-color);\n  }\n  .md-search__input::placeholder {\n    color: var(--md-primary-bg-color--light);\n  }\n  .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input {\n    color: var(--md-default-fg-color);\n    text-overflow: clip;\n    background-color: var(--md-default-bg-color);\n    border-radius: 0.1rem 0.1rem 0 0;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle=search]:checked ~ .md-header .md-search__input::placeholder {\n    color: var(--md-default-fg-color--light);\n  }\n}\n.md-search__icon {\n  position: absolute;\n  z-index: 2;\n  width: 1.2rem;\n  height: 1.2rem;\n  cursor: pointer;\n  transition: color 250ms, opacity 250ms;\n}\n.md-search__icon:hover {\n  opacity: 0.7;\n}\n.md-search__icon[for=__search] {\n  top: 0.3rem;\n  left: 0.5rem;\n}\n[dir=rtl] .md-search__icon[for=__search] {\n  right: 0.5rem;\n  left: initial;\n}\n[dir=rtl] .md-search__icon[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[for=__search] {\n    top: 0.6rem;\n    left: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[for=__search] {\n    right: 0.8rem;\n    left: initial;\n  }\n  .md-search__icon[for=__search] svg:first-child {\n    display: none;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__icon[for=__search] {\n    pointer-events: none;\n  }\n  .md-search__icon[for=__search] svg:last-child {\n    display: none;\n  }\n}\n.md-search__icon[type=reset] {\n  top: 0.3rem;\n  right: 0.5rem;\n  transform: scale(0.75);\n  opacity: 0;\n  transition: transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-search__icon[type=reset] {\n  right: initial;\n  left: 0.5rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[type=reset] {\n    top: 0.6rem;\n    right: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[type=reset] {\n    right: initial;\n    left: 0.8rem;\n  }\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset] {\n  transform: scale(1);\n  opacity: 1;\n  pointer-events: initial;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset]:hover {\n  opacity: 0.7;\n}\n.md-search__output {\n  position: absolute;\n  z-index: 1;\n  width: 100%;\n  overflow: hidden;\n  border-radius: 0 0 0.1rem 0.1rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__output {\n    top: 2.4rem;\n    bottom: 0;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__output {\n    top: 1.9rem;\n    opacity: 0;\n    transition: opacity 400ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__output {\n    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.4);\n    opacity: 1;\n  }\n}\n.md-search__scrollwrap {\n  height: 100%;\n  overflow-y: auto;\n  background-color: var(--md-default-bg-color);\n  backface-visibility: hidden;\n  touch-action: pan-y;\n}\n@media (max-resolution: 1dppx) {\n  .md-search__scrollwrap {\n    transform: translateZ(0);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search__scrollwrap {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-search__scrollwrap {\n    width: 34.4rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__scrollwrap {\n    max-height: 0;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__scrollwrap {\n    max-height: 75vh;\n  }\n  .md-search__scrollwrap:hover {\n    scrollbar-color: var(--md-accent-fg-color) transparent;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar {\n    width: 0.2rem;\n    height: 0.2rem;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb {\n    background-color: var(--md-default-fg-color--lighter);\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb:hover {\n    background-color: var(--md-accent-fg-color);\n  }\n}\n\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n}\n.md-search-result__meta {\n  padding: 0 0.8rem;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.8rem;\n  background-color: var(--md-default-fg-color--lightest);\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__meta {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__meta {\n    padding-right: 2.2rem;\n    padding-left: initial;\n  }\n}\n.md-search-result__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-search-result__item {\n  box-shadow: 0 -0.05rem 0 var(--md-default-fg-color--lightest);\n}\n.md-search-result__item:first-child {\n  box-shadow: none;\n}\n.md-search-result__link {\n  display: block;\n  outline: none;\n  transition: background-color 250ms;\n  scroll-snap-align: start;\n}\n.md-search-result__link:focus, .md-search-result__link:hover {\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__link:last-child p:last-child {\n  margin-bottom: 0.6rem;\n}\n.md-search-result__more summary {\n  display: block;\n  padding: 0.75em 0.8rem;\n  color: var(--md-typeset-a-color);\n  font-size: 0.64rem;\n  outline: 0;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__more summary {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__more summary {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__more summary:focus, .md-search-result__more summary:hover {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__more summary::marker, .md-search-result__more summary::-webkit-details-marker {\n  display: none;\n}\n.md-search-result__more summary ~ * > * {\n  opacity: 0.65;\n}\n.md-search-result__article {\n  position: relative;\n  padding: 0 0.8rem;\n  overflow: hidden;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__article {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__article {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__article--document .md-search-result__title {\n  margin: 0.55rem 0;\n  font-weight: 400;\n  font-size: 0.8rem;\n  line-height: 1.4;\n}\n.md-search-result__icon {\n  position: absolute;\n  left: 0;\n  width: 1.2rem;\n  height: 1.2rem;\n  margin: 0.5rem;\n  color: var(--md-default-fg-color--light);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search-result__icon {\n    display: none;\n  }\n}\n.md-search-result__icon::after {\n  display: inline-block;\n  width: 100%;\n  height: 100%;\n  background-color: currentColor;\n  mask-image: var(--md-search-result-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-search-result__icon {\n  right: 0;\n  left: initial;\n}\n[dir=rtl] .md-search-result__icon::after {\n  transform: scaleX(-1);\n}\n.md-search-result__title {\n  margin: 0.5em 0;\n  font-weight: 700;\n  font-size: 0.64rem;\n  line-height: 1.6;\n}\n.md-search-result__teaser {\n  display: -webkit-box;\n  max-height: 2rem;\n  margin: 0.5em 0;\n  overflow: hidden;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.6;\n  text-overflow: ellipsis;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n.md-search-result__teaser mark {\n  text-decoration: underline;\n  background-color: transparent;\n}\n.md-search-result__terms {\n  margin: 0.5em 0;\n  font-size: 0.64rem;\n  font-style: italic;\n}\n.md-search-result mark {\n  color: var(--md-accent-fg-color);\n  background-color: transparent;\n}\n\n.md-select {\n  position: relative;\n  z-index: 1;\n}\n.md-select__inner {\n  position: absolute;\n  top: calc(100% - 0.2rem);\n  left: 50%;\n  max-height: 0;\n  margin-top: 0.2rem;\n  color: var(--md-default-fg-color);\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  transform: translate3d(-50%, 0.3rem, 0);\n  opacity: 0;\n  transition: transform 250ms 375ms, opacity 250ms 250ms, max-height 0ms 500ms;\n}\n.md-select:focus-within .md-select__inner, .md-select:hover .md-select__inner {\n  max-height: 10rem;\n  transform: translate3d(-50%, 0, 0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms, max-height 250ms;\n}\n.md-select__inner::after {\n  position: absolute;\n  top: 0;\n  left: 50%;\n  width: 0;\n  height: 0;\n  margin-top: -0.2rem;\n  margin-left: -0.2rem;\n  border: 0.2rem solid transparent;\n  border-top: 0;\n  border-bottom-color: var(--md-default-bg-color);\n  content: \"\";\n}\n.md-select__list {\n  max-height: inherit;\n  margin: 0;\n  padding: 0;\n  overflow: auto;\n  font-size: 0.8rem;\n  list-style-type: none;\n  border-radius: 0.1rem;\n}\n.md-select__item {\n  line-height: 1.8rem;\n}\n.md-select__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  cursor: pointer;\n  transition: background-color 250ms, color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-select__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-select__link:focus, .md-select__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n.md-sidebar {\n  position: sticky;\n  top: 2.4rem;\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: 12.1rem;\n  padding: 1.2rem 0;\n}\n@media print {\n  .md-sidebar {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-sidebar--primary {\n    position: fixed;\n    top: 0;\n    left: -12.1rem;\n    z-index: 3;\n    display: block;\n    width: 12.1rem;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n    transform: translateX(0);\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms;\n  }\n  [dir=rtl] .md-sidebar--primary {\n    right: -12.1rem;\n    left: initial;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);\n    transform: translateX(12.1rem);\n  }\n  [dir=rtl] [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    transform: translateX(-12.1rem);\n  }\n  .md-sidebar--primary .md-sidebar__scrollwrap {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    margin: 0;\n    scroll-snap-type: none;\n    overflow: hidden;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar {\n    height: 0;\n  }\n  .no-js .md-sidebar {\n    height: auto;\n  }\n}\n.md-sidebar--secondary {\n  display: none;\n  order: 2;\n}\n@media screen and (min-width: 60em) {\n  .md-sidebar--secondary {\n    height: 0;\n  }\n  .no-js .md-sidebar--secondary {\n    height: auto;\n  }\n  .md-sidebar--secondary:not([hidden]) {\n    display: block;\n  }\n  .md-sidebar--secondary .md-sidebar__scrollwrap {\n    touch-action: pan-y;\n  }\n}\n.md-sidebar__scrollwrap {\n  margin: 0 0.2rem;\n  overflow-y: auto;\n  backface-visibility: hidden;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-sidebar__scrollwrap:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n\n@media screen and (max-width: 76.1875em) {\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    opacity: 0;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-overlay {\n    width: 100%;\n    height: 100%;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n  100% {\n    height: 0.65rem;\n  }\n}\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n  50% {\n    opacity: 0;\n  }\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n:root {\n  --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n  --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n  --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n  --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n.md-source {\n  display: block;\n  font-size: 0.65rem;\n  line-height: 1.2;\n  white-space: nowrap;\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n}\n.md-source:hover {\n  opacity: 0.7;\n}\n.md-source__icon {\n  display: inline-block;\n  width: 2rem;\n  height: 2.4rem;\n  vertical-align: middle;\n}\n.md-source__icon svg {\n  margin-top: 0.6rem;\n  margin-left: 0.6rem;\n}\n[dir=rtl] .md-source__icon svg {\n  margin-right: 0.6rem;\n  margin-left: initial;\n}\n.md-source__icon + .md-source__repository {\n  margin-left: -2rem;\n  padding-left: 2rem;\n}\n[dir=rtl] .md-source__icon + .md-source__repository {\n  margin-right: -2rem;\n  margin-left: initial;\n  padding-right: 2rem;\n  padding-left: initial;\n}\n.md-source__repository {\n  display: inline-block;\n  max-width: calc(100% - 1.2rem);\n  margin-left: 0.6rem;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  vertical-align: middle;\n}\n.md-source__facts {\n  margin: 0.1rem 0 0;\n  padding: 0;\n  overflow: hidden;\n  font-size: 0.55rem;\n  list-style-type: none;\n  opacity: 0.75;\n}\n[data-md-state=done] .md-source__facts {\n  animation: md-source__facts--done 250ms ease-in;\n}\n.md-source__fact {\n  display: inline-block;\n}\n[data-md-state=done] .md-source__fact {\n  animation: md-source__fact--done 400ms ease-out;\n}\n.md-source__fact::before {\n  display: inline-block;\n  width: 0.6rem;\n  height: 0.6rem;\n  margin-right: 0.1rem;\n  vertical-align: text-top;\n  background-color: currentColor;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-source__fact:nth-child(1n+2)::before {\n  margin-left: 0.4rem;\n}\n[dir=rtl] .md-source__fact {\n  margin-right: initial;\n  margin-left: 0.1rem;\n}\n[dir=rtl] .md-source__fact:nth-child(1n+2)::before {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-source__fact--version::before {\n  mask-image: var(--md-source-version-icon);\n}\n.md-source__fact--stars::before {\n  mask-image: var(--md-source-stars-icon);\n}\n.md-source__fact--forks::before {\n  mask-image: var(--md-source-forks-icon);\n}\n.md-source__fact--repositories::before {\n  mask-image: var(--md-source-repositories-icon);\n}\n\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n}\n@media print {\n  .md-tabs {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-tabs {\n    display: none;\n  }\n}\n.md-tabs[data-md-state=hidden] {\n  pointer-events: none;\n}\n.md-tabs__list {\n  margin: 0;\n  margin-left: 0.2rem;\n  padding: 0;\n  white-space: nowrap;\n  list-style: none;\n  contain: content;\n}\n[dir=rtl] .md-tabs__list {\n  margin-right: 0.2rem;\n  margin-left: initial;\n}\n.md-tabs__item {\n  display: inline-block;\n  height: 2.4rem;\n  padding-right: 0.6rem;\n  padding-left: 0.6rem;\n}\n.md-tabs__link {\n  display: block;\n  margin-top: 0.8rem;\n  font-size: 0.7rem;\n  backface-visibility: hidden;\n  opacity: 0.7;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms;\n}\n.md-tabs__link--active, .md-tabs__link:focus, .md-tabs__link:hover {\n  color: inherit;\n  opacity: 1;\n}\n.md-tabs__item:nth-child(2) .md-tabs__link {\n  transition-delay: 20ms;\n}\n.md-tabs__item:nth-child(3) .md-tabs__link {\n  transition-delay: 40ms;\n}\n.md-tabs__item:nth-child(4) .md-tabs__link {\n  transition-delay: 60ms;\n}\n.md-tabs__item:nth-child(5) .md-tabs__link {\n  transition-delay: 80ms;\n}\n.md-tabs__item:nth-child(6) .md-tabs__link {\n  transition-delay: 100ms;\n}\n.md-tabs__item:nth-child(7) .md-tabs__link {\n  transition-delay: 120ms;\n}\n.md-tabs__item:nth-child(8) .md-tabs__link {\n  transition-delay: 140ms;\n}\n.md-tabs__item:nth-child(9) .md-tabs__link {\n  transition-delay: 160ms;\n}\n.md-tabs__item:nth-child(10) .md-tabs__link {\n  transition-delay: 180ms;\n}\n.md-tabs__item:nth-child(11) .md-tabs__link {\n  transition-delay: 200ms;\n}\n.md-tabs__item:nth-child(12) .md-tabs__link {\n  transition-delay: 220ms;\n}\n.md-tabs__item:nth-child(13) .md-tabs__link {\n  transition-delay: 240ms;\n}\n.md-tabs__item:nth-child(14) .md-tabs__link {\n  transition-delay: 260ms;\n}\n.md-tabs__item:nth-child(15) .md-tabs__link {\n  transition-delay: 280ms;\n}\n.md-tabs__item:nth-child(16) .md-tabs__link {\n  transition-delay: 300ms;\n}\n.md-tabs[data-md-state=hidden] .md-tabs__link {\n  transform: translateY(50%);\n  opacity: 0;\n  transition: transform 0ms 100ms, opacity 100ms;\n}\n\n.md-top {\n  position: sticky;\n  bottom: 0.4rem;\n  z-index: 1;\n  float: right;\n  margin: -2.8rem 0.4rem 0.4rem;\n  padding: 0.4rem;\n  color: var(--md-primary-bg-color);\n  background: var(--md-primary-fg-color);\n  border-radius: 100%;\n  outline: none;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transform: translateY(0);\n  transition: opacity 125ms, transform 125ms cubic-bezier(0.4, 0, 0.2, 1), background-color 125ms;\n}\n[dir=rtl] .md-top {\n  float: left;\n}\n.md-top[data-md-state=hidden] {\n  transform: translateY(-0.2rem);\n  opacity: 0;\n}\n.md-top:focus, .md-top:hover {\n  background: var(--md-accent-fg-color);\n  transform: scale(1.1);\n}\n\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n.md-version {\n  flex-shrink: 0;\n  height: 2.4rem;\n  font-size: 0.8rem;\n}\n.md-version__current {\n  position: relative;\n  top: 0.05rem;\n  margin-right: 0.4rem;\n  margin-left: 1.4rem;\n}\n[dir=rtl] .md-version__current {\n  margin-right: 1.4rem;\n  margin-left: 0.4rem;\n}\n.md-version__current::after {\n  display: inline-block;\n  width: 0.4rem;\n  height: 0.6rem;\n  margin-left: 0.4rem;\n  background-color: currentColor;\n  mask-image: var(--md-version-icon);\n  mask-repeat: no-repeat;\n  content: \"\";\n}\n[dir=rtl] .md-version__current::after {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-version__list {\n  position: absolute;\n  top: 0.15rem;\n  z-index: 1;\n  max-height: 1.8rem;\n  margin: 0.2rem 0.8rem;\n  padding: 0;\n  overflow: auto;\n  color: var(--md-default-fg-color);\n  list-style-type: none;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  opacity: 0;\n  transition: max-height 0ms 500ms, opacity 250ms 250ms;\n  scroll-snap-type: y mandatory;\n}\n.md-version__list:focus-within, .md-version__list:hover {\n  max-height: 10rem;\n  opacity: 1;\n  transition: max-height 250ms, opacity 250ms;\n}\n.md-version__item {\n  line-height: 1.8rem;\n}\n.md-version__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  white-space: nowrap;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-version__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-version__link:focus, .md-version__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n:root {\n  --md-admonition-icon--note:\n    svg-load(\"material/pencil.svg\");\n  --md-admonition-icon--abstract:\n    svg-load(\"material/text-subject.svg\");\n  --md-admonition-icon--info:\n    svg-load(\"material/information.svg\");\n  --md-admonition-icon--tip:\n    svg-load(\"material/fire.svg\");\n  --md-admonition-icon--success:\n    svg-load(\"material/check-circle.svg\");\n  --md-admonition-icon--question:\n    svg-load(\"material/help-circle.svg\");\n  --md-admonition-icon--warning:\n    svg-load(\"material/alert.svg\");\n  --md-admonition-icon--failure:\n    svg-load(\"material/close-circle.svg\");\n  --md-admonition-icon--danger:\n    svg-load(\"material/flash-circle.svg\");\n  --md-admonition-icon--bug:\n    svg-load(\"material/bug.svg\");\n  --md-admonition-icon--example:\n    svg-load(\"material/format-list-numbered.svg\");\n  --md-admonition-icon--quote:\n    svg-load(\"material/format-quote-close.svg\");\n}\n\n.md-typeset .admonition, .md-typeset details {\n  margin: 1.5625em 0;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  color: var(--md-admonition-fg-color);\n  font-size: 0.64rem;\n  page-break-inside: avoid;\n  background-color: var(--md-admonition-bg-color);\n  border-left: 0.2rem solid #448aff;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.05);\n}\n@media print {\n  .md-typeset .admonition, .md-typeset details {\n    box-shadow: none;\n  }\n}\n[dir=rtl] .md-typeset .admonition, [dir=rtl] .md-typeset details {\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\n.md-typeset .admonition .admonition, .md-typeset details .admonition, .md-typeset .admonition details, .md-typeset details details {\n  margin-top: 1em;\n  margin-bottom: 1em;\n}\n.md-typeset .admonition .md-typeset__scrollwrap, .md-typeset details .md-typeset__scrollwrap {\n  margin: 1em -0.6rem;\n}\n.md-typeset .admonition .md-typeset__table, .md-typeset details .md-typeset__table {\n  padding: 0 0.6rem;\n}\n.md-typeset .admonition > .tabbed-set:only-child, .md-typeset details > .tabbed-set:only-child {\n  margin-top: 0;\n}\nhtml .md-typeset .admonition > :last-child, html .md-typeset details > :last-child {\n  margin-bottom: 0.6rem;\n}\n.md-typeset .admonition-title, .md-typeset summary {\n  position: relative;\n  margin: 0 -0.6rem 0 -0.8rem;\n  padding: 0.4rem 0.6rem 0.4rem 2rem;\n  font-weight: 700;\n  background-color: rgba(68, 138, 255, 0.1);\n  border-left: 0.2rem solid #448aff;\n}\n[dir=rtl] .md-typeset .admonition-title, [dir=rtl] .md-typeset summary {\n  margin: 0 -0.8rem 0 -0.6rem;\n  padding: 0.4rem 2rem 0.4rem 0.6rem;\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\nhtml .md-typeset .admonition-title:last-child, html .md-typeset summary:last-child {\n  margin-bottom: 0;\n}\n.md-typeset .admonition-title::before, .md-typeset summary::before {\n  position: absolute;\n  left: 0.6rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .admonition-title::before, [dir=rtl] .md-typeset summary::before {\n  right: 0.6rem;\n  left: initial;\n}\n.md-typeset .admonition-title + .tabbed-set:last-child, .md-typeset summary + .tabbed-set:last-child {\n  margin-top: 0;\n}\n\n.md-typeset .admonition.note, .md-typeset details.note {\n  border-color: #448aff;\n}\n\n.md-typeset .note > .admonition-title, .md-typeset .note > summary {\n  background-color: rgba(68, 138, 255, 0.1);\n  border-color: #448aff;\n}\n.md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before {\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.abstract, .md-typeset details.abstract, .md-typeset .admonition.tldr, .md-typeset details.tldr, .md-typeset .admonition.summary, .md-typeset details.summary {\n  border-color: #00b0ff;\n}\n\n.md-typeset .abstract > .admonition-title, .md-typeset .abstract > summary, .md-typeset .tldr > .admonition-title, .md-typeset .tldr > summary, .md-typeset .summary > .admonition-title, .md-typeset .summary > summary {\n  background-color: rgba(0, 176, 255, 0.1);\n  border-color: #00b0ff;\n}\n.md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before, .md-typeset .tldr > .admonition-title::before, .md-typeset .tldr > summary::before, .md-typeset .summary > .admonition-title::before, .md-typeset .summary > summary::before {\n  background-color: #00b0ff;\n  mask-image: var(--md-admonition-icon--abstract);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.info, .md-typeset details.info, .md-typeset .admonition.todo, .md-typeset details.todo {\n  border-color: #00b8d4;\n}\n\n.md-typeset .info > .admonition-title, .md-typeset .info > summary, .md-typeset .todo > .admonition-title, .md-typeset .todo > summary {\n  background-color: rgba(0, 184, 212, 0.1);\n  border-color: #00b8d4;\n}\n.md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before, .md-typeset .todo > .admonition-title::before, .md-typeset .todo > summary::before {\n  background-color: #00b8d4;\n  mask-image: var(--md-admonition-icon--info);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.tip, .md-typeset details.tip, .md-typeset .admonition.important, .md-typeset details.important, .md-typeset .admonition.hint, .md-typeset details.hint {\n  border-color: #00bfa5;\n}\n\n.md-typeset .tip > .admonition-title, .md-typeset .tip > summary, .md-typeset .important > .admonition-title, .md-typeset .important > summary, .md-typeset .hint > .admonition-title, .md-typeset .hint > summary {\n  background-color: rgba(0, 191, 165, 0.1);\n  border-color: #00bfa5;\n}\n.md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before, .md-typeset .important > .admonition-title::before, .md-typeset .important > summary::before, .md-typeset .hint > .admonition-title::before, .md-typeset .hint > summary::before {\n  background-color: #00bfa5;\n  mask-image: var(--md-admonition-icon--tip);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.success, .md-typeset details.success, .md-typeset .admonition.done, .md-typeset details.done, .md-typeset .admonition.check, .md-typeset details.check {\n  border-color: #00c853;\n}\n\n.md-typeset .success > .admonition-title, .md-typeset .success > summary, .md-typeset .done > .admonition-title, .md-typeset .done > summary, .md-typeset .check > .admonition-title, .md-typeset .check > summary {\n  background-color: rgba(0, 200, 83, 0.1);\n  border-color: #00c853;\n}\n.md-typeset .success > .admonition-title::before, .md-typeset .success > summary::before, .md-typeset .done > .admonition-title::before, .md-typeset .done > summary::before, .md-typeset .check > .admonition-title::before, .md-typeset .check > summary::before {\n  background-color: #00c853;\n  mask-image: var(--md-admonition-icon--success);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.question, .md-typeset details.question, .md-typeset .admonition.faq, .md-typeset details.faq, .md-typeset .admonition.help, .md-typeset details.help {\n  border-color: #64dd17;\n}\n\n.md-typeset .question > .admonition-title, .md-typeset .question > summary, .md-typeset .faq > .admonition-title, .md-typeset .faq > summary, .md-typeset .help > .admonition-title, .md-typeset .help > summary {\n  background-color: rgba(100, 221, 23, 0.1);\n  border-color: #64dd17;\n}\n.md-typeset .question > .admonition-title::before, .md-typeset .question > summary::before, .md-typeset .faq > .admonition-title::before, .md-typeset .faq > summary::before, .md-typeset .help > .admonition-title::before, .md-typeset .help > summary::before {\n  background-color: #64dd17;\n  mask-image: var(--md-admonition-icon--question);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.warning, .md-typeset details.warning, .md-typeset .admonition.attention, .md-typeset details.attention, .md-typeset .admonition.caution, .md-typeset details.caution {\n  border-color: #ff9100;\n}\n\n.md-typeset .warning > .admonition-title, .md-typeset .warning > summary, .md-typeset .attention > .admonition-title, .md-typeset .attention > summary, .md-typeset .caution > .admonition-title, .md-typeset .caution > summary {\n  background-color: rgba(255, 145, 0, 0.1);\n  border-color: #ff9100;\n}\n.md-typeset .warning > .admonition-title::before, .md-typeset .warning > summary::before, .md-typeset .attention > .admonition-title::before, .md-typeset .attention > summary::before, .md-typeset .caution > .admonition-title::before, .md-typeset .caution > summary::before {\n  background-color: #ff9100;\n  mask-image: var(--md-admonition-icon--warning);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.failure, .md-typeset details.failure, .md-typeset .admonition.missing, .md-typeset details.missing, .md-typeset .admonition.fail, .md-typeset details.fail {\n  border-color: #ff5252;\n}\n\n.md-typeset .failure > .admonition-title, .md-typeset .failure > summary, .md-typeset .missing > .admonition-title, .md-typeset .missing > summary, .md-typeset .fail > .admonition-title, .md-typeset .fail > summary {\n  background-color: rgba(255, 82, 82, 0.1);\n  border-color: #ff5252;\n}\n.md-typeset .failure > .admonition-title::before, .md-typeset .failure > summary::before, .md-typeset .missing > .admonition-title::before, .md-typeset .missing > summary::before, .md-typeset .fail > .admonition-title::before, .md-typeset .fail > summary::before {\n  background-color: #ff5252;\n  mask-image: var(--md-admonition-icon--failure);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.danger, .md-typeset details.danger, .md-typeset .admonition.error, .md-typeset details.error {\n  border-color: #ff1744;\n}\n\n.md-typeset .danger > .admonition-title, .md-typeset .danger > summary, .md-typeset .error > .admonition-title, .md-typeset .error > summary {\n  background-color: rgba(255, 23, 68, 0.1);\n  border-color: #ff1744;\n}\n.md-typeset .danger > .admonition-title::before, .md-typeset .danger > summary::before, .md-typeset .error > .admonition-title::before, .md-typeset .error > summary::before {\n  background-color: #ff1744;\n  mask-image: var(--md-admonition-icon--danger);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.bug, .md-typeset details.bug {\n  border-color: #f50057;\n}\n\n.md-typeset .bug > .admonition-title, .md-typeset .bug > summary {\n  background-color: rgba(245, 0, 87, 0.1);\n  border-color: #f50057;\n}\n.md-typeset .bug > .admonition-title::before, .md-typeset .bug > summary::before {\n  background-color: #f50057;\n  mask-image: var(--md-admonition-icon--bug);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.example, .md-typeset details.example {\n  border-color: #7c4dff;\n}\n\n.md-typeset .example > .admonition-title, .md-typeset .example > summary {\n  background-color: rgba(124, 77, 255, 0.1);\n  border-color: #7c4dff;\n}\n.md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before {\n  background-color: #7c4dff;\n  mask-image: var(--md-admonition-icon--example);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.quote, .md-typeset details.quote, .md-typeset .admonition.cite, .md-typeset details.cite {\n  border-color: #9e9e9e;\n}\n\n.md-typeset .quote > .admonition-title, .md-typeset .quote > summary, .md-typeset .cite > .admonition-title, .md-typeset .cite > summary {\n  background-color: rgba(158, 158, 158, 0.1);\n  border-color: #9e9e9e;\n}\n.md-typeset .quote > .admonition-title::before, .md-typeset .quote > summary::before, .md-typeset .cite > .admonition-title::before, .md-typeset .cite > summary::before {\n  background-color: #9e9e9e;\n  mask-image: var(--md-admonition-icon--quote);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n.md-typeset [id^=\"fnref:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n}\n.md-typeset [id^=\"fn:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n}\n.md-typeset .footnote {\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n}\n.md-typeset .footnote > ol {\n  margin-left: 0;\n}\n.md-typeset .footnote > ol > li {\n  transition: color 125ms;\n}\n.md-typeset .footnote > ol > li:target {\n  color: var(--md-default-fg-color);\n}\n.md-typeset .footnote > ol > li:hover .footnote-backref, .md-typeset .footnote > ol > li:target .footnote-backref {\n  transform: translateX(0);\n  opacity: 1;\n}\n.md-typeset .footnote > ol > li > :first-child {\n  margin-top: 0;\n}\n.md-typeset .footnote-backref {\n  display: inline-block;\n  color: var(--md-typeset-a-color);\n  font-size: 0;\n  vertical-align: text-bottom;\n  transform: translateX(0.25rem);\n  opacity: 0;\n  transition: color 250ms, transform 250ms 250ms, opacity 125ms 250ms;\n}\n@media print {\n  .md-typeset .footnote-backref {\n    color: var(--md-typeset-a-color);\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n[dir=rtl] .md-typeset .footnote-backref {\n  transform: translateX(-0.25rem);\n}\n.md-typeset .footnote-backref:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset .footnote-backref::before {\n  display: inline-block;\n  width: 0.8rem;\n  height: 0.8rem;\n  background-color: currentColor;\n  mask-image: var(--md-footnotes-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .footnote-backref::before svg {\n  transform: scaleX(-1);\n}\n\n.md-typeset .headerlink {\n  display: inline-block;\n  margin-left: 0.5rem;\n  color: var(--md-default-fg-color--lighter);\n  opacity: 0;\n  transition: color 250ms, opacity 125ms;\n}\n@media print {\n  .md-typeset .headerlink {\n    display: none;\n  }\n}\n[dir=rtl] .md-typeset .headerlink {\n  margin-right: 0.5rem;\n  margin-left: initial;\n}\n.md-typeset :hover > .headerlink,\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus {\n  opacity: 1;\n  transition: color 250ms, opacity 125ms;\n}\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus,\n.md-typeset .headerlink:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset :target {\n  scroll-margin-top: 3.6rem;\n}\n.md-typeset h1:target,\n.md-typeset h2:target,\n.md-typeset h3:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h1:target::before,\n.md-typeset h2:target::before,\n.md-typeset h3:target::before {\n  display: block;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n  content: \"\";\n}\n.md-typeset h4:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h4:target::before {\n  display: block;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n  content: \"\";\n}\n.md-typeset h5:target,\n.md-typeset h6:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h5:target::before,\n.md-typeset h6:target::before {\n  display: block;\n  margin-top: -3.6rem;\n  padding-top: 3.6rem;\n  content: \"\";\n}\n\n.md-typeset div.arithmatex {\n  overflow: auto;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset div.arithmatex {\n    margin: 0 -0.8rem;\n  }\n}\n.md-typeset div.arithmatex > * {\n  width: min-content;\n  margin: 1em auto !important;\n  padding: 0 0.8rem;\n  touch-action: auto;\n}\n\n.md-typeset del.critic,\n.md-typeset ins.critic,\n.md-typeset .critic.comment {\n  box-decoration-break: clone;\n}\n.md-typeset del.critic {\n  background-color: var(--md-typeset-del-color);\n}\n.md-typeset ins.critic {\n  background-color: var(--md-typeset-ins-color);\n}\n.md-typeset .critic.comment {\n  color: var(--md-code-hl-comment-color);\n}\n.md-typeset .critic.comment::before {\n  content: \"/* \";\n}\n.md-typeset .critic.comment::after {\n  content: \" */\";\n}\n.md-typeset .critic.block {\n  display: block;\n  margin: 1em 0;\n  padding-right: 0.8rem;\n  padding-left: 0.8rem;\n  overflow: auto;\n  box-shadow: none;\n}\n.md-typeset .critic.block > :first-child {\n  margin-top: 0.5em;\n}\n.md-typeset .critic.block > :last-child {\n  margin-bottom: 0.5em;\n}\n\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n.md-typeset details {\n  display: flow-root;\n  padding-top: 0;\n  overflow: visible;\n}\n.md-typeset details[open] > summary::after {\n  transform: rotate(90deg);\n}\n.md-typeset details:not([open]) {\n  padding-bottom: 0;\n  box-shadow: none;\n}\n.md-typeset details:not([open]) > summary {\n  border-radius: 0.1rem;\n}\n.md-typeset details::after {\n  display: table;\n  content: \"\";\n}\n.md-typeset summary {\n  display: block;\n  min-height: 1rem;\n  padding: 0.4rem 1.8rem 0.4rem 2rem;\n  border-top-left-radius: 0.1rem;\n  border-top-right-radius: 0.1rem;\n  cursor: pointer;\n}\n[dir=rtl] .md-typeset summary {\n  padding: 0.4rem 2.2rem 0.4rem 1.8rem;\n}\n.md-typeset summary:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset summary::after {\n  position: absolute;\n  top: 0.4rem;\n  right: 0.4rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: currentColor;\n  mask-image: var(--md-details-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  transform: rotate(0deg);\n  transition: transform 250ms;\n  content: \"\";\n}\n[dir=rtl] .md-typeset summary::after {\n  right: initial;\n  left: 0.4rem;\n  transform: rotate(180deg);\n}\n.md-typeset summary::marker, .md-typeset summary::-webkit-details-marker {\n  display: none;\n}\n\n.md-typeset .emojione,\n.md-typeset .twemoji,\n.md-typeset .gemoji {\n  display: inline-flex;\n  height: 1.125em;\n  vertical-align: text-top;\n}\n.md-typeset .emojione svg,\n.md-typeset .twemoji svg,\n.md-typeset .gemoji svg {\n  width: 1.125em;\n  max-height: 100%;\n  fill: currentColor;\n}\n\n.highlight .o,\n.highlight .ow {\n  color: var(--md-code-hl-operator-color);\n}\n.highlight .p {\n  color: var(--md-code-hl-punctuation-color);\n}\n.highlight .cpf,\n.highlight .l,\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .s2,\n.highlight .si,\n.highlight .s1,\n.highlight .ss {\n  color: var(--md-code-hl-string-color);\n}\n.highlight .cp,\n.highlight .se,\n.highlight .sh,\n.highlight .sr,\n.highlight .sx {\n  color: var(--md-code-hl-special-color);\n}\n.highlight .m,\n.highlight .mb,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo {\n  color: var(--md-code-hl-number-color);\n}\n.highlight .k,\n.highlight .kd,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .kc,\n.highlight .n {\n  color: var(--md-code-hl-name-color);\n}\n.highlight .no,\n.highlight .nb,\n.highlight .bp {\n  color: var(--md-code-hl-constant-color);\n}\n.highlight .nc,\n.highlight .ne,\n.highlight .nf,\n.highlight .nn {\n  color: var(--md-code-hl-function-color);\n}\n.highlight .nd,\n.highlight .ni,\n.highlight .nl,\n.highlight .nt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .c,\n.highlight .cm,\n.highlight .c1,\n.highlight .ch,\n.highlight .cs,\n.highlight .sd {\n  color: var(--md-code-hl-comment-color);\n}\n.highlight .na,\n.highlight .nv,\n.highlight .vc,\n.highlight .vg,\n.highlight .vi {\n  color: var(--md-code-hl-variable-color);\n}\n.highlight .ge,\n.highlight .gr,\n.highlight .gh,\n.highlight .go,\n.highlight .gp,\n.highlight .gs,\n.highlight .gu,\n.highlight .gt {\n  color: var(--md-code-hl-generic-color);\n}\n.highlight .gd,\n.highlight .gi {\n  margin: 0 -0.125em;\n  padding: 0 0.125em;\n  border-radius: 0.1rem;\n}\n.highlight .gd {\n  background-color: var(--md-typeset-del-color);\n}\n.highlight .gi {\n  background-color: var(--md-typeset-ins-color);\n}\n.highlight .hll {\n  display: block;\n  margin: 0 -1.1764705882em;\n  padding: 0 1.1764705882em;\n  background-color: var(--md-code-hl-color);\n}\n.highlight [data-linenos]::before {\n  position: sticky;\n  left: -1.1764705882em;\n  float: left;\n  margin-right: 1.1764705882em;\n  margin-left: -1.1764705882em;\n  padding-left: 1.1764705882em;\n  color: var(--md-default-fg-color--light);\n  background-color: var(--md-code-bg-color);\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n  content: attr(data-linenos);\n  user-select: none;\n}\n\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n}\n.highlighttable tbody,\n.highlighttable td {\n  display: block;\n  padding: 0;\n}\n.highlighttable tr {\n  display: flex;\n}\n.highlighttable pre {\n  margin: 0;\n}\n.highlighttable .linenos {\n  padding: 0.7720588235em 1.1764705882em;\n  padding-right: 0;\n  font-size: 0.85em;\n  background-color: var(--md-code-bg-color);\n  user-select: none;\n}\n.highlighttable .linenodiv {\n  padding-right: 0.5882352941em;\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n}\n.highlighttable .linenodiv pre {\n  color: var(--md-default-fg-color--light);\n  text-align: right;\n}\n.highlighttable .code {\n  flex: 1;\n  overflow: hidden;\n}\n\n.md-typeset .highlighttable {\n  margin: 1em 0;\n  direction: ltr;\n  border-radius: 0.1rem;\n}\n.md-typeset .highlighttable code {\n  border-radius: 0;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > .highlight {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > .highlight .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n  .md-typeset > .highlight code {\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable {\n    margin: 1em -0.8rem;\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n}\n\n.md-typeset .keys kbd::before,\n.md-typeset .keys kbd::after {\n  position: relative;\n  margin: 0;\n  color: inherit;\n  -moz-osx-font-smoothing: initial;\n  -webkit-font-smoothing: initial;\n}\n.md-typeset .keys span {\n  padding: 0 0.2em;\n  color: var(--md-default-fg-color--light);\n}\n.md-typeset .keys .key-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-left-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-right-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-left-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-right-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-left-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-right-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-left-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-right-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-left-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-right-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-left-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-right-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-left-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-right-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-left-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-right-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-arrow-down::before {\n  padding-right: 0.4em;\n  content: \"↓\";\n}\n.md-typeset .keys .key-arrow-left::before {\n  padding-right: 0.4em;\n  content: \"←\";\n}\n.md-typeset .keys .key-arrow-right::before {\n  padding-right: 0.4em;\n  content: \"→\";\n}\n.md-typeset .keys .key-arrow-up::before {\n  padding-right: 0.4em;\n  content: \"↑\";\n}\n.md-typeset .keys .key-backspace::before {\n  padding-right: 0.4em;\n  content: \"⌫\";\n}\n.md-typeset .keys .key-backtab::before {\n  padding-right: 0.4em;\n  content: \"⇤\";\n}\n.md-typeset .keys .key-caps-lock::before {\n  padding-right: 0.4em;\n  content: \"⇪\";\n}\n.md-typeset .keys .key-clear::before {\n  padding-right: 0.4em;\n  content: \"⌧\";\n}\n.md-typeset .keys .key-context-menu::before {\n  padding-right: 0.4em;\n  content: \"☰\";\n}\n.md-typeset .keys .key-delete::before {\n  padding-right: 0.4em;\n  content: \"⌦\";\n}\n.md-typeset .keys .key-eject::before {\n  padding-right: 0.4em;\n  content: \"⏏\";\n}\n.md-typeset .keys .key-end::before {\n  padding-right: 0.4em;\n  content: \"⤓\";\n}\n.md-typeset .keys .key-escape::before {\n  padding-right: 0.4em;\n  content: \"⎋\";\n}\n.md-typeset .keys .key-home::before {\n  padding-right: 0.4em;\n  content: \"⤒\";\n}\n.md-typeset .keys .key-insert::before {\n  padding-right: 0.4em;\n  content: \"⎀\";\n}\n.md-typeset .keys .key-page-down::before {\n  padding-right: 0.4em;\n  content: \"⇟\";\n}\n.md-typeset .keys .key-page-up::before {\n  padding-right: 0.4em;\n  content: \"⇞\";\n}\n.md-typeset .keys .key-print-screen::before {\n  padding-right: 0.4em;\n  content: \"⎙\";\n}\n.md-typeset .keys .key-tab::after {\n  padding-left: 0.4em;\n  content: \"⇥\";\n}\n.md-typeset .keys .key-num-enter::after {\n  padding-left: 0.4em;\n  content: \"⌤\";\n}\n.md-typeset .keys .key-enter::after {\n  padding-left: 0.4em;\n  content: \"⏎\";\n}\n\n.md-typeset .tabbed-content {\n  display: none;\n  order: 99;\n  width: 100%;\n  box-shadow: 0 -0.05rem var(--md-default-fg-color--lightest);\n}\n@media print {\n  .md-typeset .tabbed-content {\n    display: block;\n    order: initial;\n  }\n}\n.md-typeset .tabbed-content > pre:only-child,\n.md-typeset .tabbed-content > .highlight:only-child pre,\n.md-typeset .tabbed-content > .highlighttable:only-child {\n  margin: 0;\n}\n.md-typeset .tabbed-content > pre:only-child > code,\n.md-typeset .tabbed-content > .highlight:only-child pre > code,\n.md-typeset .tabbed-content > .highlighttable:only-child > code {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.md-typeset .tabbed-content > .tabbed-set {\n  margin: 0;\n}\n.md-typeset .tabbed-set {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 1em 0;\n  border-radius: 0.1rem;\n}\n.md-typeset .tabbed-set > input {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-typeset .tabbed-set > input:checked + label {\n  color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .tabbed-set > input:checked + label + .tabbed-content {\n  display: block;\n}\n.md-typeset .tabbed-set > input:focus + label {\n  outline-style: auto;\n}\n.md-typeset .tabbed-set > input:not(.focus-visible) + label {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset .tabbed-set > label {\n  z-index: 1;\n  width: auto;\n  padding: 0.9375em 1.25em 0.78125em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.64rem;\n  border-bottom: 0.1rem solid transparent;\n  cursor: pointer;\n  transition: color 250ms;\n}\n.md-typeset .tabbed-set > label:hover {\n  color: var(--md-accent-fg-color);\n}\n\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n.md-typeset .task-list-item {\n  position: relative;\n  list-style-type: none;\n}\n.md-typeset .task-list-item [type=checkbox] {\n  position: absolute;\n  top: 0.45em;\n  left: -2em;\n}\n[dir=rtl] .md-typeset .task-list-item [type=checkbox] {\n  right: -2em;\n  left: initial;\n}\n.md-typeset .task-list-control [type=checkbox] {\n  z-index: -1;\n  opacity: 0;\n}\n.md-typeset .task-list-indicator::before {\n  position: absolute;\n  top: 0.15em;\n  left: -1.5em;\n  width: 1.25em;\n  height: 1.25em;\n  background-color: var(--md-default-fg-color--lightest);\n  mask-image: var(--md-tasklist-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .task-list-indicator::before {\n  right: -1.5em;\n  left: initial;\n}\n.md-typeset [type=checkbox]:checked + .task-list-indicator::before {\n  background-color: #00e676;\n  mask-image: var(--md-tasklist-icon--checked);\n}\n\n@media screen and (min-width: 45em) {\n  .md-typeset .inline {\n    float: left;\n    width: 11.7rem;\n    margin-top: 0;\n    margin-right: 0.8rem;\n    margin-bottom: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  .md-typeset .inline.end {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline.end {\n    float: left;\n    margin-right: 0.8rem;\n    margin-left: 0;\n  }\n}\n\n/*# sourceMappingURL=main.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Enforce correct box model and prevent adjustments of font size after\n// orientation changes in IE and iOS\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n// All elements shall inherit the document default\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\n// Remove margin in all browsers\nbody {\n  margin: 0;\n}\n\n// Reset tap outlines on iOS and Android\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\n// Reset link styles\na {\n  color: inherit;\n  text-decoration: none;\n}\n\n// Normalize horizontal separator styles\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: px2rem(1px);\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\n// Normalize font-size in all browsers\nsmall {\n  font-size: 80%;\n}\n\n// Prevent subscript and superscript from affecting line-height\nsub,\nsup {\n  line-height: 1em;\n}\n\n// Remove border on image\nimg {\n  border-style: none;\n}\n\n// Reset table styles\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\n// Reset table cell styles\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\n// Reset button styles\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\n// Reset input styles\ninput {\n  border: 0;\n  outline: none;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Color definitions\n:root {\n\n  // Default color shades\n  --md-default-fg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter:      hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest:     hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter:      hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest:     hsla(0, 0%, 100%, 0.12);\n\n  // Primary color shades\n  --md-primary-fg-color:               hsla(#{hex2hsl($clr-indigo-500)}, 1);\n  --md-primary-fg-color--light:        hsla(#{hex2hsl($clr-indigo-400)}, 1);\n  --md-primary-fg-color--dark:         hsla(#{hex2hsl($clr-indigo-700)}, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Accent color shades\n  --md-accent-fg-color:                hsla(#{hex2hsl($clr-indigo-a200)}, 1);\n  --md-accent-fg-color--transparent:   hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);\n  --md-accent-bg-color:                hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light:         hsla(0, 0%, 100%, 0.7);\n\n  // Light theme (default)\n  > * {\n\n    // Code color shades\n    --md-code-fg-color:                hsla(200, 18%, 26%, 1);\n    --md-code-bg-color:                hsla(0, 0%, 96%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n    --md-code-hl-number-color:         hsla(0, 67%, 50%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 47%, 1);\n    --md-code-hl-function-color:       hsla(291, 45%, 50%, 1);\n    --md-code-hl-constant-color:       hsla(250, 63%, 60%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 54%, 51%, 1);\n    --md-code-hl-string-color:         hsla(150, 63%, 30%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n\n    // Typeset `del` and `ins` color shades\n    --md-typeset-del-color:            hsla(6, 90%, 60%, 0.15);\n    --md-typeset-ins-color:            hsla(150, 90%, 44%, 0.15);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(0, 0%, 98%, 1);\n    --md-typeset-kbd-accent-color:     hsla(0, 100%, 100%, 1);\n    --md-typeset-kbd-border-color:     hsla(0, 0%, 72%, 1);\n\n    // Admonition color shades\n    --md-admonition-fg-color:          var(--md-default-fg-color);\n    --md-admonition-bg-color:          var(--md-default-bg-color);\n\n    // Footer color shades\n    --md-footer-fg-color:              hsla(0, 0%, 100%, 1);\n    --md-footer-fg-color--light:       hsla(0, 0%, 100%, 0.7);\n    --md-footer-fg-color--lighter:     hsla(0, 0%, 100%, 0.3);\n    --md-footer-bg-color:              hsla(0, 0%, 0%, 0.87);\n    --md-footer-bg-color--dark:        hsla(0, 0%, 0%, 0.32);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon\n.md-icon {\n\n  // SVG defaults\n  svg {\n    display: block;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    fill: currentColor;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: font definitions\n// ----------------------------------------------------------------------------\n\n// Enable font-smoothing in Webkit and FF\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// Define default fonts\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family:\n    var(--md-text-font-family, _),\n    -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\n// Define monospaced fonts\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family:\n    var(--md-code-font-family, _),\n    SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: typesetted content\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Content that is typeset - if possible, all margins, paddings and font sizes\n// should be set in ems, so nested blocks (e.g. admonitions) render correctly.\n.md-typeset {\n  font-size: px2rem(16px);\n  line-height: 1.6;\n  color-adjust: exact;\n\n  // [print]: We'll use a smaller `font-size` for printing, so code examples\n  // don't break too early, and `16px` looks too big anyway.\n  @media print {\n    font-size: px2rem(13.6px);\n  }\n\n  // Default spacing\n  ul,\n  ol,\n  dl,\n  figure,\n  blockquote,\n  pre {\n    display: flow-root;\n    margin: 1em 0;\n  }\n\n  // Headline on level 1\n  h1 {\n    margin: 0 0 px2em(40px, 32px);\n    color: var(--md-default-fg-color--light);\n    font-weight: 300;\n    font-size: px2em(32px);\n    line-height: 1.3;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 2\n  h2 {\n    margin: px2em(40px, 25px) 0 px2em(16px, 25px);\n    font-weight: 300;\n    font-size: px2em(25px);\n    line-height: 1.4;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3\n  h3 {\n    margin: px2em(32px, 20px) 0 px2em(16px, 20px);\n    font-weight: 400;\n    font-size: px2em(20px);\n    line-height: 1.5;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3 following level 2\n  h2 + h3 {\n    margin-top: px2em(16px, 20px);\n  }\n\n  // Headline on level 4\n  h4 {\n    margin: px2em(16px) 0;\n    font-weight: 700;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5-6\n  h5,\n  h6 {\n    margin: px2em(16px, 12.8px) 0;\n    color: var(--md-default-fg-color--light);\n    font-weight: 700;\n    font-size: px2em(12.8px);\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5\n  h5 {\n    text-transform: uppercase;\n  }\n\n  // Horizontal separator\n  hr {\n    display: flow-root;\n    margin: 1.5em 0;\n    border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);\n  }\n\n  // Text link\n  a {\n    color: var(--md-typeset-a-color);\n    word-break: break-word;\n\n    // Also enable color transition on pseudo elements\n    &,\n    &::before {\n      transition: color 125ms;\n    }\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Code block\n  code,\n  pre,\n  kbd {\n    color: var(--md-code-fg-color);\n    direction: ltr;\n\n    // [print]: Wrap text and hide scollbars\n    @media print {\n      white-space: pre-wrap;\n    }\n  }\n\n  // Inline code block\n  code {\n    padding: 0 px2em(4px, 13.6px);\n    font-size: px2em(13.6px);\n    word-break: break-word;\n    background-color: var(--md-code-bg-color);\n    border-radius: px2rem(2px);\n    box-decoration-break: clone;\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n  }\n\n  // Code block in headline\n  h1 code,\n  h2 code,\n  h3 code,\n  h4 code,\n  h5 code,\n  h6 code {\n    margin: initial;\n    padding: initial;\n    background-color: transparent;\n    box-shadow: none;\n  }\n\n  // Ensure link color in code blocks\n  a code {\n    color: currentColor;\n  }\n\n  // Unformatted content\n  pre {\n    position: relative;\n    line-height: 1.4;\n\n    // Code block\n    > code {\n      display: block;\n      margin: 0;\n      padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n      overflow: auto;\n      word-break: normal;\n      box-shadow: none;\n      box-decoration-break: slice;\n      touch-action: auto;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Code block on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Unformatted text\n    > pre {\n      margin: 1em px2rem(-16px);\n\n      // Code block\n      code {\n        border-radius: 0;\n      }\n    }\n  }\n\n  // Keyboard key\n  kbd {\n    display: inline-block;\n    padding: 0 px2em(8px, 12px);\n    color: var(--md-default-fg-color);\n    font-size: px2em(12px);\n    vertical-align: text-top;\n    word-break: break-word;\n    background-color: var(--md-typeset-kbd-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(2px)  0 px2rem(1px) var(--md-typeset-kbd-border-color),\n      0 px2rem(2px)  0             var(--md-typeset-kbd-border-color),\n      0 px2rem(-2px) px2rem(4px)   var(--md-typeset-kbd-accent-color) inset;\n  }\n\n  // Text highlighting marker\n  mark {\n    color: inherit;\n    word-break: break-word;\n    background-color: var(--md-typeset-mark-color);\n    box-decoration-break: clone;\n  }\n\n  // Abbreviation\n  abbr {\n    text-decoration: none;\n    border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);\n    cursor: help;\n\n    // Show tooltip for touch devices\n    @media (hover: none) {\n      position: relative;\n\n      // Tooltip\n      &[title]:focus::after,\n      &[title]:hover::after {\n        @include z-depth(2);\n\n        position: absolute;\n        left: 0;\n        display: inline-block;\n        width: auto;\n        min-width: max-content;\n        max-width: 80%;\n        margin-top: 2em;\n        padding: px2rem(4px) px2rem(6px);\n        color: var(--md-default-bg-color);\n        font-size: px2rem(14px);\n        background-color: var(--md-default-fg-color);\n        border-radius: px2rem(2px);\n        content: attr(title);\n      }\n    }\n  }\n\n  // Small text\n  small {\n    opacity: 0.75;\n  }\n\n  // Superscript and subscript\n  sup,\n  sub {\n    margin-left: px2em(1px, 12.8px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(1px, 12.8px);\n      margin-left: initial;\n    }\n  }\n\n  // Blockquotes, possibly nested\n  blockquote {\n    padding-left: px2rem(12px);\n    color: var(--md-default-fg-color--light);\n    border-left: px2rem(4px) solid var(--md-default-fg-color--lighter);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: initial;\n      border-right: px2rem(4px) solid var(--md-default-fg-color--lighter);\n      border-left: initial;\n    }\n  }\n\n  // Unordered list\n  ul {\n    list-style-type: disc;\n  }\n\n  // Unordered and ordered list\n  ul,\n  ol {\n    margin-left: px2em(10px);\n    padding: 0;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(10px);\n      margin-left: initial;\n    }\n\n    // Nested ordered list\n    ol {\n      list-style-type: lower-alpha;\n\n      // Triply nested ordered list\n      ol {\n        list-style-type: lower-roman;\n      }\n    }\n\n    // List element\n    li {\n      margin-bottom: 0.5em;\n      margin-left: px2em(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2em(20px);\n        margin-left: initial;\n      }\n\n      // Adjust spacing\n      p,\n      blockquote {\n        margin: 0.5em 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Nested list\n      ul,\n      ol {\n        margin: 0.5em 0 0.5em px2em(10px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2em(10px);\n          margin-left: initial;\n        }\n      }\n    }\n  }\n\n  // Definition list\n  dd {\n    margin: 1em 0 1.5em px2em(30px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(30px);\n      margin-left: initial;\n    }\n  }\n\n  // Image or icon\n  img,\n  svg {\n    max-width: 100%;\n    height: auto;\n\n    // Adjust spacing when left-aligned\n    &[align=\"left\"] {\n      margin: 1em;\n      margin-left: 0;\n    }\n\n    // Adjust spacing when right-aligned\n    &[align=\"right\"] {\n      margin: 1em;\n      margin-right: 0;\n    }\n\n    // Adjust spacing when sole children\n    &[align]:only-child {\n      margin-top: 0;\n    }\n  }\n\n  // Figure\n  figure {\n    width: fit-content;\n    max-width: 100%;\n    margin: 0 auto;\n    text-align: center;\n\n    // Figure images\n    img {\n      display: block;\n    }\n  }\n\n  // Figure caption\n  figcaption {\n    max-width: px2rem(480px);\n    margin: 1em auto 2em;\n    font-style: italic;\n  }\n\n  // Limit width to container\n  iframe {\n    max-width: 100%;\n  }\n\n  // Data table\n  table:not([class]) {\n    display: inline-block;\n    max-width: 100%;\n    overflow: auto;\n    font-size: px2rem(12.8px);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    touch-action: auto;\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: table;\n    }\n\n    // Due to margin collapse because of the necessary inline-block hack, we\n    // cannot increase the bottom margin on the table, so we just increase the\n    // top margin on the following element\n    + * {\n      margin-top: 1.5em;\n    }\n\n    // Elements in table heading and cell\n    th > *,\n    td > * {\n\n      // Adjust spacing on first child\n      &:first-child {\n        margin-top: 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    // Table heading and cell\n    th:not([align]),\n    td:not([align]) {\n      text-align: left;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        text-align: right;\n      }\n    }\n\n    // Table heading\n    th {\n      min-width: px2rem(100px);\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      color: var(--md-default-bg-color);\n      vertical-align: top;\n      background-color: var(--md-default-fg-color--light);\n\n      // Links in table headings\n      a {\n        color: inherit;\n      }\n    }\n\n    // Table cell\n    td {\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      vertical-align: top;\n      border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n    }\n\n    // Table row\n    tr {\n      transition: background-color 125ms;\n\n      // Table row on hover\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.035);\n        box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;\n      }\n\n      // Hide border on first table row\n      &:first-child td {\n        border-top: 0;\n      }\n    }\n\n    // Text link in table\n    a {\n      word-break: normal;\n    }\n  }\n\n  // Sortable table\n  table th[role=\"columnheader\"] {\n    cursor: pointer;\n\n    // Sort icon\n    &::after {\n      display: inline-block;\n      width: 1.2em;\n      height: 1.2em;\n      margin-left: 0.5em;\n      vertical-align: sub;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Sort ascending\n    &[aria-sort=\"ascending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--ascending);\n    }\n\n    // Sort descending\n    &[aria-sort=\"descending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--descending);\n    }\n  }\n\n  // Data table scroll wrapper\n  &__scrollwrap {\n    margin: 1em px2rem(-16px);\n    overflow-x: auto;\n    touch-action: auto;\n  }\n\n  // Data table wrapper\n  &__table {\n    display: inline-block;\n    margin-bottom: 0.5em;\n    padding: 0 px2rem(16px);\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: block;\n    }\n\n    // Data table\n    html & table {\n      display: table;\n      width: 100%;\n      margin: 0;\n      overflow: hidden;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","//\n// Name:           Material Shadows\n// Description:    Mixins for Material Design Shadows.\n// Version:        3.0.1\n//\n// Author:         Denis Malinochkin\n// Git:            https://github.com/mrmlnc/material-shadows\n//\n// twitter:        @mrmlnc\n//\n// ------------------------------------\n\n\n// Mixins\n// ------------------------------------\n\n@mixin z-depth-transition() {\n  transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1);\n}\n\n@mixin z-depth-focus() {\n  box-shadow: 0 0 8px rgba(0, 0, 0, .18), 0 8px 16px rgba(0, 0, 0, .36);\n}\n\n@mixin z-depth-2dp() {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14),\n              0 1px 5px 0 rgba(0, 0, 0, .12),\n              0 3px 1px -2px rgba(0, 0, 0, .2);\n}\n\n@mixin z-depth-3dp() {\n  box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .14),\n              0 1px 8px 0 rgba(0, 0, 0, .12),\n              0 3px 3px -2px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-4dp() {\n  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14),\n              0 1px 10px 0 rgba(0, 0, 0, .12),\n              0 2px 4px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-6dp() {\n  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14),\n              0 1px 18px 0 rgba(0, 0, 0, .12),\n              0 3px 5px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-8dp() {\n  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14),\n              0 3px 14px 2px rgba(0, 0, 0, .12),\n              0 5px 5px -3px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-16dp() {\n  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14),\n              0  6px 30px 5px rgba(0, 0, 0, .12),\n              0  8px 10px -5px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-24dp() {\n  box-shadow: 0  9px 46px  8px rgba(0, 0, 0, .14),\n              0 24px 38px  3px rgba(0, 0, 0, .12),\n              0 11px 15px -7px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth($dp: 2) {\n  @if $dp == 2 {\n    @include z-depth-2dp();\n  } @else if $dp == 3 {\n    @include z-depth-3dp();\n  } @else if $dp == 4 {\n    @include z-depth-4dp();\n  } @else if $dp == 6 {\n    @include z-depth-6dp();\n  } @else if $dp == 8 {\n    @include z-depth-8dp();\n  } @else if $dp == 16 {\n    @include z-depth-16dp();\n  } @else if $dp == 24 {\n    @include z-depth-24dp();\n  }\n}\n\n\n// Class generator\n// ------------------------------------\n\n@mixin z-depth-classes($transition: false, $focus: false) {\n  @if $transition == true {\n    &-transition {\n      @include z-depth-transition();\n    }\n  }\n\n  @if $focus == true {\n    &-focus {\n      @include z-depth-focus();\n    }\n  }\n\n  // The available values for the shadow depth\n  @each $depth in 2, 3, 4, 6, 8, 16, 24 {\n    &-#{$depth}dp {\n      @include z-depth($depth);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: base grid and containers\n// ----------------------------------------------------------------------------\n\n// Stretch container to viewport and set base `font-size`\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  // Hack: normally, we would set the base `font-size` to `62.5%`, so we can\n  // base all calculations on `10px`, but Chromium and Chrome define a minimal\n  // `font-size` of `12px` if the system language is set to Chinese. For this\n  // reason we just double the `font-size` and set it to `20px`.\n  //\n  // See https://github.com/squidfunk/mkdocs-material/issues/911\n  font-size: 125%;\n\n  // [screen medium +]: Set base `font-size` to `11px`\n  @include break-from-device(screen medium) {\n    font-size: 137.5%;\n  }\n\n  // [screen large +]: Set base `font-size` to `12px`\n  @include break-from-device(screen large) {\n    font-size: 150%;\n  }\n}\n\n// Stretch body to container - flexbox is used, so the footer will always be\n// aligned to the bottom of the viewport\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  // Hack: reset `font-size` to `10px`, so the spacing for all inline elements\n  // is correct again. Otherwise the spacing would be based on `20px`.\n  font-size: px2rem(10px);\n  background-color: var(--md-default-bg-color);\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n\n  // Body in locked state\n  &[data-md-state=\"lock\"] {\n\n    // [tablet portrait -]: Omit scroll bubbling\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Grid container - this class is applied to wrapper elements within the\n// header, content area and footer, and makes sure that their width is limited\n// to `1220px`, and they are rendered centered if the screen is larger.\n.md-grid {\n  max-width: px2rem(1220px);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n// Main container\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n}\n\n// Main area - stretch to remaining space of container\n.md-main {\n  flex-grow: 1;\n\n  // Main area wrapper\n  &__inner {\n    display: flex;\n    height: 100%;\n    margin-top: px2rem(24px + 6px);\n  }\n}\n\n// Add ellipsis in case of overflowing text\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: navigational elements\n// ----------------------------------------------------------------------------\n\n// Toggle - this class is applied to checkbox elements, which are used to\n// implement the CSS-only drawer and navigation, as well as the search\n.md-toggle {\n  display: none;\n}\n\n// Option - this class is applied to radio elements, which are used to\n// implement the color palette toggle\n.md-option {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n\n  // Option label for checked radio button\n  &:checked + label:not([hidden]) {\n    display: block;\n  }\n\n  // Option label on focus\n  &.focus-visible + label {\n    outline-style: auto;\n  }\n}\n\n// Skip link\n.md-skip {\n  position: fixed;\n  // Hack: if we don't set the negative `z-index`, the skip link will force the\n  // creation of new layers when code blocks are near the header on scrolling\n  z-index: -1;\n  margin: px2rem(10px);\n  padding: px2rem(6px) px2rem(10px);\n  color: var(--md-default-bg-color);\n  font-size: px2rem(12.8px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(px2rem(8px));\n  opacity: 0;\n\n  // Show skip link on focus\n  &:focus {\n    z-index: 10;\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n      opacity   175ms 75ms;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: print styles\n// ----------------------------------------------------------------------------\n\n// Add margins to page\n@page {\n  margin: 25mm;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Announcement bar\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide announcement bar\n  @media print {\n    display: none;\n  }\n\n  // Announcement wrapper\n  &__inner {\n    margin: px2rem(12px) auto;\n    padding: 0 px2rem(16px);\n    color: var(--md-footer-fg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Button to copy to clipboard\n.md-clipboard {\n  position: absolute;\n  top: px2em(8px);\n  right: px2em(8px);\n  z-index: 1;\n  width: px2em(24px);\n  height: px2em(24px);\n  color: var(--md-default-fg-color--lightest);\n  border-radius: px2rem(2px);\n  cursor: pointer;\n  transition: color 250ms;\n\n  // [print]: Hide button\n  @media print {\n    display: none;\n  }\n\n  // Hide outline for pointer devices\n  &:not(.focus-visible) {\n    outline: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  // Darken color on code block hover\n  :hover > & {\n    color: var(--md-default-fg-color--light);\n  }\n\n  // Button on focus/hover\n  &:focus,\n  &:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Button icon - the width and height are defined in `em`, so the size is\n  // automatically adjusted for nested code blocks (e.g. in admonitions)\n  &::after {\n    display: block;\n    width: px2em(18px);\n    height: px2em(18px);\n    margin: 0 auto;\n    background-color: currentColor;\n    mask-image: var(--md-clipboard-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n\n  // Inline button\n  &--inline {\n    cursor: pointer;\n\n    // Code block\n    code {\n      transition:\n        color            250ms,\n        background-color 250ms;\n    }\n\n    // Code block on focus/hover\n    &:focus code,\n    &:hover code {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Content area\n.md-content {\n  flex-grow: 1;\n  // Hack: we must use `overflow: hidden`, so the content area is capped by\n  // the dimensions of its parent. Otherwise, long code blocks might lead to\n  // a wider content area which will break everything. This, however, induces\n  // margin collapse, which will break scroll margins. Adding a large enough\n  // scroll padding seems to do the trick, at least in Chrome and Firefox.\n  overflow: hidden;\n  scroll-padding-top: px2rem(1024px);\n\n  // Content wrapper\n  &__inner {\n    margin: 0 px2rem(16px) px2rem(24px);\n    padding-top: px2rem(12px);\n\n    // [screen +]: Adjust spacing between content area and sidebars\n    @include break-from-device(screen) {\n\n      // Sidebar with navigation is visible\n      .md-sidebar--primary:not([hidden]) ~ .md-content > & {\n        margin-left: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(24px);\n          margin-left: px2rem(16px);\n        }\n      }\n\n      // Sidebar with table of contents is visible\n      .md-sidebar--secondary:not([hidden]) ~ .md-content > & {\n        margin-right: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(16px);\n          margin-left: px2rem(24px);\n        }\n      }\n    }\n\n    // Hack: add pseudo element for spacing, as the overflow of the content\n    // container may not be hidden due to an imminent offset error on targets\n    &::before {\n      display: block;\n      height: px2rem(8px);\n      content: \"\";\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  // Button inside of the content area - these buttons are meant for actions on\n  // a document-level, i.e. linking to related source code files, printing etc.\n  &__button {\n    float: right;\n    margin: px2rem(8px) 0;\n    margin-left: px2rem(8px);\n    padding: 0;\n\n    // [print]: Hide buttons\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: left;\n      margin-right: px2rem(8px);\n      margin-left: initial;\n\n      // Flip icon vertically\n      svg {\n        transform: scaleX(-1);\n      }\n    }\n\n    // Adjust default link color for icons\n    .md-typeset & {\n      color: var(--md-default-fg-color--lighter);\n    }\n\n    // Align with body copy located next to icon\n    svg {\n      display: inline;\n      vertical-align: top;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Dialog\n.md-dialog {\n  @include z-depth(2);\n\n  position: fixed;\n  right: px2rem(16px);\n  bottom: px2rem(16px);\n  left: initial;\n  z-index: 2;\n  min-width: px2rem(222px);\n  padding: px2rem(8px) px2rem(12px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(100%);\n  opacity: 0;\n  transition:\n    transform 0ms   400ms,\n    opacity   400ms;\n  pointer-events: none;\n\n  // [print]: Hide dialog\n  @media print {\n    display: none;\n  }\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    right: initial;\n    left: px2rem(16px);\n  }\n\n  // Dialog in open state\n  &[data-md-state=\"open\"] {\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),\n      opacity   400ms;\n    pointer-events: initial;\n  }\n\n  // Dialog wrapper\n  &__inner {\n    color: var(--md-default-bg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Form button\n  .md-button {\n    display: inline-block;\n    padding: px2em(10px) px2em(32px);\n    color: var(--md-primary-fg-color);\n    font-weight: 700;\n    border: px2rem(2px) solid currentColor;\n    border-radius: px2rem(2px);\n    transition:\n      color            125ms,\n      background-color 125ms,\n      border-color     125ms;\n\n    // Primary button\n    &--primary {\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color);\n      border-color: var(--md-primary-fg-color);\n    }\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-bg-color);\n      background-color: var(--md-accent-fg-color);\n      border-color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Form input\n  .md-input {\n    height: px2rem(36px);\n    padding: 0 px2rem(12px);\n    font-size: px2rem(16px);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    transition: box-shadow 250ms;\n\n    // Input on focus/hover\n    &:focus,\n    &:hover {\n      box-shadow:\n        0 px2rem(8px)   px2rem(20px) hsla(0, 0%, 0%, 0.15),\n        0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.15);\n    }\n\n    // Stretch to full width\n    &--stretch {\n      width: 100%;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Header - by default, the header will be sticky and stay always on top of the\n// viewport. If this behavior is not desired, just set `position: static`.\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  // Hack: reduce jitter by adding a transparent box shadow of the same size\n  // so the size of the layer doesn't change during animation\n  box-shadow:\n    0 0           px2rem(4px) rgba(0, 0, 0, 0),\n    0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);\n\n  // [print]: Hide header\n  @media print {\n    display: none;\n  }\n\n  // Header in shadow state, i.e. shadow is visible\n  &[data-md-state=\"shadow\"] {\n    box-shadow:\n      0 0           px2rem(4px) rgba(0, 0, 0, 0.1),\n      0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);\n    transition:\n      transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      box-shadow 250ms;\n  }\n\n  // Header in hidden state, i.e. moved out of sight\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(-100%);\n    transition:\n      transform  250ms cubic-bezier(0.8, 0, 0.6, 1),\n      box-shadow 250ms;\n  }\n\n  // Link or button on focus\n  .focus-visible {\n    outline-color: currentColor;\n  }\n\n  // Header wrapper\n  &__inner {\n    display: flex;\n    align-items: center;\n    padding: 0 px2rem(4px);\n  }\n\n  // Header button\n  &__button {\n    position: relative;\n    z-index: 1;\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n    color: currentColor;\n    vertical-align: middle;\n    cursor: pointer;\n    transition: opacity 250ms;\n\n    // Button on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Header button is visible\n    &:not([hidden]) {\n      display: inline-block;\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Button with logo, pointing to `config.site_url`\n    &.md-logo {\n      margin: px2rem(4px);\n      padding: px2rem(8px);\n\n      // [tablet -]: Hide button\n      @include break-to-device(tablet) {\n        display: none;\n      }\n\n      // Image or icon\n      img,\n      svg {\n        display: block;\n        width: px2rem(24px);\n        height: px2rem(24px);\n        fill: currentColor;\n      }\n    }\n\n    // Button for search\n    &[for=\"__search\"] {\n\n      // [tablet landscape +]: Hide button\n      @include break-from-device(tablet landscape) {\n        display: none;\n      }\n\n      // [no-js]: Hide button\n      .no-js & {\n        display: none;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Button for drawer\n    &[for=\"__drawer\"] {\n\n      // [screen +]: Hide button\n      @include break-from-device(screen) {\n        display: none;\n      }\n    }\n  }\n\n  // Header topic\n  &__topic {\n    position: absolute;\n    display: flex;\n    max-width: 100%;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   150ms;\n\n    // Second header topic - title of the current page\n    & + & {\n      z-index: -1;\n      transform: translateX(px2rem(25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(-25px));\n      }\n    }\n  }\n\n  // Header title\n  &__title {\n    flex-grow: 1;\n    height: px2rem(48px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n\n    // Header title in active state, i.e. page title is visible\n    &[data-md-state=\"active\"] .md-header__topic {\n      z-index: -1;\n      transform: translateX(px2rem(-25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(25px));\n      }\n\n      // Second header topic - title of the current page\n      + .md-header__topic {\n        z-index: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms;\n        pointer-events: initial;\n      }\n    }\n\n    // Add ellipsis in case of overflowing text\n    > .md-header__ellipsis {\n      position: relative;\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  // Header option\n  &__option {\n    display: flex;\n    flex-shrink: 0;\n    max-width: 100%;\n    white-space: nowrap;\n    transition:\n      max-width  0ms 250ms,\n      opacity  250ms 250ms;\n\n    // Hide toggle when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      max-width: 0;\n      opacity: 0;\n      transition:\n        max-width 0ms,\n        opacity   0ms;\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n\n    // [tablet landscape +]: Show repository information\n    @include break-from-device(tablet landscape) {\n      display: block;\n      width: px2rem(234px);\n      max-width: px2rem(234px);\n      margin-left: px2rem(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(20px);\n        margin-left: initial;\n      }\n    }\n\n    // [screen +]: Adjust spacing of search bar\n    @include break-from-device(screen) {\n      margin-left: px2rem(28px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(28px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Footer\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide footer\n  @media print {\n    display: none;\n  }\n\n  // Footer wrapper\n  &__inner {\n    padding: px2rem(4px);\n    overflow: auto;\n  }\n\n  // Footer link to previous and next page\n  &__link {\n    display: flex;\n    padding-top: px2rem(28px);\n    padding-bottom: px2rem(8px);\n    transition: opacity 250ms;\n\n    // [tablet +]: Adjust width to 50/50\n    @include break-from-device(tablet) {\n      width: 50%;\n    }\n\n    // Footer link on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Footer link to previous page\n    &--prev {\n      float: left;\n\n      // [mobile -]: Adjust width to 25/75 and hide title\n      @include break-to-device(mobile) {\n        width: 25%;\n\n        // Hide footer title\n        .md-footer__title {\n          display: none;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Footer link to next page\n    &--next {\n      float: right;\n      text-align: right;\n\n      // [mobile -]: Adjust width to 25/75\n      @include break-to-device(mobile) {\n        width: 75%;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        text-align: left;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n\n  // Footer title\n  &__title {\n    position: relative;\n    flex-grow: 1;\n    max-width: calc(100% - #{px2rem(48px)});\n    padding: 0 px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n  }\n\n  // Footer link button\n  &__button {\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n  }\n\n  // Footer link direction (i.e. prev and next)\n  &__direction {\n    position: absolute;\n    right: 0;\n    left: 0;\n    margin-top: px2rem(-20px);\n    padding: 0 px2rem(20px);\n    font-size: px2rem(12.8px);\n    opacity: 0.7;\n  }\n}\n\n// Footer metadata\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n\n  // Footer metadata wrapper\n  &__inner {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    padding: px2rem(4px);\n  }\n\n  // Lighten color for non-hovered text links\n  html &.md-typeset a {\n    color: var(--md-footer-fg-color--light);\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-footer-fg-color);\n    }\n  }\n}\n\n// Footer copyright and theme information\n.md-footer-copyright {\n  width: 100%;\n  margin: auto px2rem(12px);\n  padding: px2rem(8px) 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: px2rem(12.8px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    width: auto;\n  }\n\n  // Footer copyright highlight - this is the upper part of the copyright and\n  // theme information, which will include a darker color than the theme link\n  &__highlight {\n    color: var(--md-footer-fg-color--light);\n  }\n}\n\n// Footer social links\n.md-footer-social {\n  margin: 0 px2rem(8px);\n  padding: px2rem(4px) 0 px2rem(12px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    padding: px2rem(12px) 0;\n  }\n\n  // Footer social link\n  &__link {\n    display: inline-block;\n    width: px2rem(32px);\n    height: px2rem(32px);\n    text-align: center;\n\n    // Adjust line-height to match height for correct alignment\n    &::before {\n      line-height: 1.9;\n    }\n\n    // Fill icon with current color\n    svg {\n      max-height: px2rem(16px);\n      vertical-align: -25%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Navigation\n.md-nav {\n  font-size: px2rem(14px);\n  line-height: 1.3;\n\n  // Navigation title\n  &__title {\n    display: block;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n\n    // Navigaton button\n    .md-nav__button {\n      display: none;\n\n      // Stretch images based on height, as it's the smaller dimension\n      img {\n        width: auto;\n        height: 100%;\n      }\n\n      // Button with logo, pointing to `config.site_url`\n      &.md-logo {\n\n        // Image or icon\n        img,\n        svg {\n          display: block;\n          width: px2rem(48px);\n          height: px2rem(48px);\n          fill: currentColor;\n        }\n      }\n    }\n  }\n\n  // Navigation list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Navigation item\n  &__item {\n    padding: 0 px2rem(12px);\n\n    // Navigation item on level 2\n    & & {\n      padding-right: 0;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(12px);\n        padding-left: 0;\n      }\n    }\n  }\n\n  // Navigation link\n  &__link {\n    display: block;\n    margin-top: 0.625em;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    cursor: pointer;\n    transition: color 125ms;\n    scroll-snap-align: start;\n\n    // Link in blurred state\n    &[data-md-state=\"blur\"] {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Active link\n    .md-nav__item &--active {\n      color: var(--md-typeset-a-color);\n    }\n\n    // Navigation link in nested list\n    .md-nav__item--nested > & {\n      color: inherit;\n    }\n\n    // Navigation link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Navigation link to table of contents\n    .md-nav--primary &[for=\"__toc\"] {\n      display: none;\n\n      // Table of contents icon\n      .md-icon::after {\n        display: block;\n        width: 100%;\n        height: 100%;\n        mask-image: var(--md-toc-icon);\n        background-color: currentColor;\n      }\n\n      // Hide table of contents\n      ~ .md-nav {\n        display: none;\n      }\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Primary and nested navigation\n    &--primary,\n    &--primary & {\n      position: absolute;\n      top: 0;\n      right: 0;\n      left: 0;\n      z-index: 1;\n      display: flex;\n      flex-direction: column;\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n    }\n\n    // Primary navigation\n    &--primary {\n\n      // Navigation title and item\n      .md-nav__title,\n      .md-nav__item {\n        font-size: px2rem(16px);\n        line-height: 1.5;\n      }\n\n      // Navigation title\n      .md-nav__title {\n        position: relative;\n        height: px2rem(112px);\n        padding: px2rem(60px) px2rem(16px) px2rem(4px);\n        color: var(--md-default-fg-color--light);\n        font-weight: 400;\n        line-height: px2rem(48px);\n        white-space: nowrap;\n        background-color: var(--md-default-fg-color--lightest);\n        cursor: pointer;\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: px2rem(8px);\n          left: px2rem(8px);\n          display: block;\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin: px2rem(4px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(8px);\n            left: initial;\n          }\n\n          // Navigation icon in link to previous level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--prev);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n\n        // Navigation list\n        ~ .md-nav__list {\n          overflow-y: auto;\n          background-color: var(--md-default-bg-color);\n          box-shadow:\n            0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;\n          scroll-snap-type: y mandatory;\n          touch-action: pan-y;\n\n          // Omit border on first child\n          > :first-child {\n            border-top: 0;\n          }\n        }\n\n        // Top-level navigation title\n        &[for=\"__drawer\"] {\n          color: var(--md-primary-bg-color);\n          background-color: var(--md-primary-fg-color);\n        }\n\n        // Button with logo, pointing to `config.site_url`\n        .md-logo {\n          position: absolute;\n          top: px2rem(4px);\n          left: px2rem(4px);\n          display: block;\n          margin: px2rem(4px);\n          padding: px2rem(8px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(4px);\n            left: initial;\n          }\n        }\n      }\n\n      // Navigation list\n      .md-nav__list {\n        flex: 1;\n      }\n\n      // Navigation item\n      .md-nav__item {\n        padding: 0;\n        border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n\n        // Navigation link in nested navigation\n        &--nested > .md-nav__link {\n          padding-right: px2rem(48px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            padding-right: px2rem(16px);\n            padding-left: px2rem(48px);\n          }\n        }\n\n        // Navigation link in active navigation\n        &--active > .md-nav__link {\n          color: var(--md-typeset-a-color);\n\n          // Navigation link on focus/hover\n          &:focus,\n          &:hover {\n            color: var(--md-accent-fg-color);\n          }\n        }\n      }\n\n      // Navigation link\n      .md-nav__link {\n        position: relative;\n        margin-top: 0;\n        padding: px2rem(12px) px2rem(16px);\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: 50%;\n          right: px2rem(12px);\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin-top: px2rem(-12px);\n          color: inherit;\n          font-size: px2rem(24px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: initial;\n            left: px2rem(12px);\n          }\n\n          // Navigation icon in link to next level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--next);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n      }\n\n      // Flip icon vertically\n      .md-nav__icon {\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] &::after {\n          transform: scale(-1);\n        }\n      }\n\n      // Table of contents contained in primary navigation\n      .md-nav--secondary {\n\n        // Navigation link - omit unnecessary layering\n        .md-nav__link {\n          position: static;\n        }\n\n        // Navigation on level 2-6\n        .md-nav {\n          position: static;\n          background-color: transparent;\n\n          // Navigation link on level 3\n          .md-nav__link {\n            padding-left: px2rem(28px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(28px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 4\n          .md-nav .md-nav__link {\n            padding-left: px2rem(40px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(40px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 5\n          .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(52px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(52px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 6\n          .md-nav .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(64px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(64px);\n              padding-left: initial;\n            }\n          }\n        }\n      }\n    }\n\n    // Table of contents\n    &--secondary {\n      background-color: transparent;\n    }\n\n    // Toggle for nested navigation\n    &__toggle ~ & {\n      display: flex;\n      transform: translateX(100%);\n      opacity: 0;\n      transition:\n        transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n        opacity   125ms 50ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(-100%);\n      }\n    }\n\n    // Show nested navigation when toggle is active\n    &__toggle:checked ~ & {\n      transform: translateX(0);\n      opacity: 1;\n      transition:\n        transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   125ms 125ms;\n\n      // Navigation list\n      > .md-nav__list {\n        // Hack: promote to own layer to reduce jitter\n        backface-visibility: hidden;\n      }\n    }\n  }\n\n  // [tablet portrait -]: Layered navigation with table of contents\n  @include break-to-device(tablet portrait) {\n\n    // Show link to table of contents\n    &--primary &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n\n    // Repository information container\n    &__source {\n      display: block;\n      padding: 0 px2rem(4px);\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color--dark);\n    }\n  }\n\n  // [tablet landscape]: Layered navigation with table of contents\n  @include break-at-device(tablet landscape) {\n\n    // Show link to integrated table of contents\n    &--integrated &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n      scroll-snap-align: initial;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n  }\n\n  // [tablet landscape +]: Tree-like table of contents\n  @include break-from-device(tablet landscape) {\n\n    // Navigation title\n    &--secondary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__toc\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n  }\n\n  // [screen +]: Tree-like navigation\n  @include break-from-device(screen) {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n\n    // Navigation title\n    &--primary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__drawer\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n\n    // Hide toggle for nested navigation\n    &__toggle ~ & {\n      display: none;\n    }\n\n    // Show nested navigation when toggle is active or indeterminate\n    &__toggle:checked ~ &,\n    &__toggle:indeterminate ~ & {\n      display: block;\n    }\n\n    // Hide navigation title in nested navigation\n    &__item--nested > & > &__title {\n      display: none;\n    }\n\n    // Navigation section\n    &__item--section {\n      display: block;\n      margin: 1.25em 0;\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Hide navigation link, as sections are always expanded\n      > .md-nav__link {\n        display: none;\n      }\n\n      // Navigation\n      > .md-nav {\n        display: block;\n\n        // Navigation title\n        > .md-nav__title {\n          display: block;\n          padding: 0;\n          pointer-events: none;\n          scroll-snap-align: start;\n        }\n\n        // Adjust spacing on next level item\n        > .md-nav__list > .md-nav__item {\n          padding: 0;\n        }\n      }\n    }\n\n    // Navigation icon\n    &__icon {\n      float: right;\n      width: px2rem(18px);\n      height: px2rem(18px);\n      transition: transform 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        transform: rotate(180deg);\n      }\n\n      // Navigation icon content\n      &::after {\n        display: inline-block;\n        width: 100%;\n        height: 100%;\n        vertical-align: px2rem(-2px);\n        background-color: currentColor;\n        mask-image: var(--md-nav-icon--next);\n        mask-repeat: no-repeat;\n        mask-size: contain;\n        content: \"\";\n      }\n\n      // Navigation icon - rotate icon when toggle is active or indeterminate\n      .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,\n      .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {\n        transform: rotate(90deg);\n      }\n    }\n\n    // Modifier for when navigation tabs are rendered\n    &--lifted {\n\n      // Hide nested items on level 1 and site title\n      > .md-nav__list > .md-nav__item--nested,\n      > .md-nav__title {\n        display: none;\n      }\n\n      // Hide level 1 items\n      > .md-nav__list > .md-nav__item {\n        display: none;\n\n        // Active parent navigation item\n        &--active {\n          display: block;\n          padding: 0;\n\n          // Hide nested links\n          > .md-nav__link {\n            display: none;\n          }\n\n          // Show title and adjust spacing\n          > .md-nav > .md-nav__title {\n            display: block;\n            padding: 0 px2rem(12px);\n            pointer-events: none;\n            scroll-snap-align: start;\n          }\n        }\n\n        // Adjust spacing for navigation item on level 2\n        > .md-nav__item {\n          padding-right: px2rem(12px);\n        }\n      }\n\n      // Hack: Always show active navigation tab on breakpoint screen, despite\n      // of checkbox being checked or not. Fixes #1655.\n      .md-nav[data-md-level=\"1\"] {\n        display: block;\n      }\n    }\n\n    // Modifier for when table of contents is rendered in primary navigation\n    &--integrated &__link[for=\"__toc\"] ~ .md-nav {\n      display: block;\n      margin-bottom: 1.25em;\n      border-left: px2rem(1px) solid var(--md-primary-fg-color);\n\n      // Hide navigation title\n      > .md-nav__title {\n        display: none;\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Search\n.md-search {\n  position: relative;\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n    padding: px2rem(4px) 0;\n  }\n\n  // [no-js]: Hide search\n  .no-js & {\n    display: none;\n  }\n\n  // Search overlay\n  &__overlay {\n    z-index: 1;\n    opacity: 0;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: absolute;\n      top: px2rem(4px);\n      left: px2rem(-44px);\n      width: px2rem(40px);\n      height: px2rem(40px);\n      overflow: hidden;\n      background-color: var(--md-default-bg-color);\n      border-radius: px2rem(20px);\n      transform-origin: center;\n      transition:\n        transform 300ms 100ms,\n        opacity   200ms 200ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-44px);\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        opacity: 1;\n        transition:\n          transform 400ms,\n          opacity   100ms;\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 0;\n      height: 0;\n      background-color: hsla(0, 0%, 0%, 0.54);\n      cursor: pointer;\n      transition:\n        width     0ms 250ms,\n        height    0ms 250ms,\n        opacity 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: 0;\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        width: 100%;\n        // Hack: when the header is translated upon scrolling, a new layer is\n        // induced, which means that the height will now refer to the height of\n        // the header, albeit positioning is fixed. This should be mitigated\n        // in all cases when setting the height to 2x the viewport.\n        height: 200vh;\n        opacity: 1;\n        transition:\n          width     0ms,\n          height    0ms,\n          opacity 250ms;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [mobile portrait -]: Scale up 45 times\n      @include break-to-device(mobile portrait) {\n        transform: scale(45);\n      }\n\n      // [mobile landscape]: Scale up 60 times\n      @include break-at-device(mobile landscape) {\n        transform: scale(60);\n      }\n\n      // [tablet portrait]: Scale up 75 times\n      @include break-at-device(tablet portrait) {\n        transform: scale(75);\n      }\n    }\n  }\n\n  // Search wrapper\n  &__inner {\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n      top: 0;\n      left: 100%;\n      z-index: 2;\n      width: 100%;\n      height: 100%;\n      transform: translateX(5%);\n      opacity: 0;\n      transition:\n        right       0ms 300ms,\n        left        0ms 300ms,\n        transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   150ms 150ms;\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        left: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          right       0ms   0ms,\n          left        0ms   0ms,\n          transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms 150ms;\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: 0;\n          left: initial;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      html [dir=\"rtl\"] & {\n        right: 100%;\n        left: initial;\n        transform: translateX(-5%);\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: relative;\n      float: right;\n      width: px2rem(234px);\n      padding: px2rem(2px) 0;\n      transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [tablet landscape]: Omit overlaying header title\n      @include break-at-device(tablet landscape) {\n        width: px2rem(468px);\n      }\n\n      // [screen +]: Match width of content area\n      @include break-from-device(screen) {\n        width: px2rem(688px);\n      }\n    }\n  }\n\n  // Search form\n  &__form {\n    position: relative;\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      border-radius: px2rem(2px);\n    }\n  }\n\n  // Search input\n  &__input {\n    position: relative;\n    z-index: 2;\n    padding: 0 px2rem(44px) 0 px2rem(72px);\n    text-overflow: ellipsis;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0 px2rem(12px) transparent;\n    transition:\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: 0 px2rem(72px) 0 px2rem(44px);\n    }\n\n    // Search placeholder\n    &::placeholder {\n      transition: color 250ms;\n    }\n\n    // Search icon and placeholder\n    ~ .md-search__icon,\n    &::placeholder {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Remove the \"x\" rendered by Internet Explorer\n    &::-ms-clear {\n      display: none;\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);\n    }\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      width: 100%;\n      height: px2rem(48px);\n      font-size: px2rem(18px);\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      width: 100%;\n      height: px2rem(36px);\n      padding-left: px2rem(44px);\n      color: inherit;\n      font-size: px2rem(16px);\n      background-color: hsla(0, 0%, 0%, 0.26);\n      border-radius: px2rem(2px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n      }\n\n      // Search icon\n      + .md-search__icon {\n        color: var(--md-primary-bg-color);\n      }\n\n      // Search placeholder\n      &::placeholder {\n        color: var(--md-primary-bg-color--light);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.12);\n      }\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        color: var(--md-default-fg-color);\n        text-overflow: clip;\n        background-color: var(--md-default-bg-color);\n        border-radius: px2rem(2px) px2rem(2px) 0 0;\n\n        // Search icon and placeholder\n        + .md-search__icon,\n        &::placeholder {\n          color: var(--md-default-fg-color--light);\n        }\n      }\n    }\n  }\n\n  // Search icon\n  &__icon {\n    position: absolute;\n    z-index: 2;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    cursor: pointer;\n    transition:\n      color   250ms,\n      opacity 250ms;\n\n    // Search icon on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Search focus button\n    &[for=\"__search\"] {\n      top: px2rem(6px);\n      left: px2rem(10px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(10px);\n        left: initial;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: px2rem(16px);\n          left: initial;\n        }\n\n        // Hide the magnifying glass\n        svg:first-child {\n          display: none;\n        }\n      }\n\n      // [tablet landscape +]: Header-embedded search\n      @include break-from-device(tablet landscape) {\n        pointer-events: none;\n\n        // Hide the back arrow\n        svg:last-child {\n          display: none;\n        }\n      }\n    }\n\n    // Search reset button\n    &[type=\"reset\"] {\n      top: px2rem(6px);\n      right: px2rem(10px);\n      transform: scale(0.75);\n      opacity: 0;\n      transition:\n        transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(10px);\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        right: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: initial;\n          left: px2rem(16px);\n        }\n      }\n\n      // Show reset button when search is active and input non-empty\n      [data-md-toggle=\"search\"]:checked ~ .md-header\n      .md-search__input:valid ~ & {\n        transform: scale(1);\n        opacity: 1;\n        pointer-events: initial;\n\n        // Search focus icon\n        &:hover {\n          opacity: 0.7;\n        }\n      }\n    }\n  }\n\n  // Search output\n  &__output {\n    position: absolute;\n    z-index: 1;\n    width: 100%;\n    overflow: hidden;\n    border-radius: 0 0 px2rem(2px) px2rem(2px);\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      top: px2rem(48px);\n      bottom: 0;\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      top: px2rem(38px);\n      opacity: 0;\n      transition: opacity 400ms;\n\n      // Show output when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        @include z-depth(6);\n\n        opacity: 1;\n      }\n    }\n  }\n\n  // Search scroll wrapper\n  &__scrollwrap {\n    height: 100%;\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping\n    // seems to be something that is not ready for prime time on some browsers.\n    // scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n\n    // Mitigiate excessive repaints on non-retina devices\n    @media (max-resolution: 1dppx) {\n      transform: translateZ(0);\n    }\n\n    // [tablet landscape]: Set fixed width to omit unnecessary reflow\n    @include break-at-device(tablet landscape) {\n      width: px2rem(468px);\n    }\n\n    // [screen +]: Set fixed width to omit unnecessary reflow\n    @include break-from-device(screen) {\n      width: px2rem(688px);\n    }\n\n    // [tablet landscape +]: Limit height to viewport\n    @include break-from-device(tablet landscape) {\n      max-height: 0;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Show scroll wrapper when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        max-height: 75vh;\n      }\n\n      // Search scroll wrapper on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n}\n\n// Search result\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n\n  // Search result metadata\n  &__meta {\n    padding: 0 px2rem(16px);\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: px2rem(36px);\n    background-color: var(--md-default-fg-color--lightest);\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Search result list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Search result item\n  &__item {\n    box-shadow: 0 px2rem(-1px) 0 var(--md-default-fg-color--lightest);\n\n    // Omit border on first child\n    &:first-child {\n      box-shadow: none;\n    }\n  }\n\n  // Search result link\n  &__link {\n    display: block;\n    outline: none;\n    transition: background-color 250ms;\n    scroll-snap-align: start;\n\n    // Search result link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Adjust spacing on last child of last link\n    &:last-child p:last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Search result more link\n  &__more summary {\n    display: block;\n    padding: px2em(12px) px2rem(16px);\n    color: var(--md-typeset-a-color);\n    font-size: px2rem(12.8px);\n    outline: 0;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result more link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n\n    // Adjust transparency of less relevant results\n    ~ * > * {\n      opacity: 0.65;\n    }\n  }\n\n  // Search result article\n  &__article {\n    position: relative;\n    padding: 0 px2rem(16px);\n    overflow: hidden;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result article document\n    &--document {\n\n      // Search result title\n      .md-search-result__title {\n        margin: px2rem(11px) 0;\n        font-weight: 400;\n        font-size: px2rem(16px);\n        line-height: 1.4;\n      }\n    }\n  }\n\n  // Search result icon\n  &__icon {\n    position: absolute;\n    left: 0;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    margin: px2rem(10px);\n    color: var(--md-default-fg-color--light);\n\n    // [tablet portrait -]: Hide icon\n    @include break-to-device(tablet portrait) {\n      display: none;\n    }\n\n    // Search result icon content\n    &::after {\n      display: inline-block;\n      width: 100%;\n      height: 100%;\n      background-color: currentColor;\n      mask-image: var(--md-search-result-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: 0;\n      left: initial;\n\n      // Flip icon vertically\n      &::after {\n        transform: scaleX(-1);\n      }\n    }\n  }\n\n  // Search result title\n  &__title {\n    margin: 0.5em 0;\n    font-weight: 700;\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n  }\n\n  // Search result teaser\n  &__teaser {\n    display: -webkit-box;\n    max-height: px2rem(40px);\n    margin: 0.5em 0;\n    overflow: hidden;\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n    text-overflow: ellipsis;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 2;\n\n    // [mobile -]: Adjust number of lines\n    @include break-to-device(mobile) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // [tablet landscape]: Adjust number of lines\n    @include break-at-device(tablet landscape) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // Search term highlighting\n    mark {\n      text-decoration: underline;\n      background-color: transparent;\n    }\n  }\n\n  // Search result terms\n  &__terms {\n    margin: 0.5em 0;\n    font-size: px2rem(12.8px);\n    font-style: italic;\n  }\n\n  // Search term highlighting\n  mark {\n    color: var(--md-accent-fg-color);\n    background-color: transparent;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Selection\n.md-select {\n  position: relative;\n  z-index: 1;\n\n  // Selection bubble\n  &__inner {\n    position: absolute;\n    top: calc(100% - #{px2rem(4px)});\n    left: 50%;\n    max-height: 0;\n    margin-top: px2rem(4px);\n    color: var(--md-default-fg-color);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    transform: translate3d(-50%, px2rem(6px), 0);\n    opacity: 0;\n    transition:\n      transform  250ms 375ms,\n      opacity    250ms 250ms,\n      max-height   0ms 500ms;\n\n    // Selection bubble on parent focus/hover\n    .md-select:focus-within &,\n    .md-select:hover & {\n      max-height: px2rem(200px);\n      transform: translate3d(-50%, 0, 0);\n      opacity: 1;\n      transition:\n        transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity    250ms,\n        max-height 250ms;\n    }\n\n    // Selection bubble handle\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 50%;\n      width: 0;\n      height: 0;\n      margin-top: px2rem(-4px);\n      margin-left: px2rem(-4px);\n      border: px2rem(4px) solid transparent;\n      border-top: 0;\n      border-bottom-color: var(--md-default-bg-color);\n      content: \"\";\n    }\n  }\n\n  // Selection list\n  &__list {\n    max-height: inherit;\n    margin: 0;\n    padding: 0;\n    overflow: auto;\n    font-size: px2rem(16px);\n    list-style-type: none;\n    border-radius: px2rem(2px);\n  }\n\n  // Selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    cursor: pointer;\n    transition:\n      background-color 250ms,\n      color            250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Sidebar\n.md-sidebar {\n  position: sticky;\n  top: px2rem(48px);\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: px2rem(242px);\n  padding: px2rem(24px) 0;\n\n  // [print]: Hide sidebar\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Show navigation as drawer\n  @include break-to-device(tablet) {\n\n    // Primary sidebar with navigation\n    &--primary {\n      position: fixed;\n      top: 0;\n      left: px2rem(-242px);\n      z-index: 3;\n      display: block;\n      width: px2rem(242px);\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n      transform: translateX(0);\n      transition:\n        transform  250ms cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-242px);\n        left: initial;\n      }\n\n      // Show sidebar when drawer is active\n      [data-md-toggle=\"drawer\"]:checked ~ .md-container & {\n        @include z-depth(8);\n\n        transform: translateX(px2rem(242px));\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          transform: translateX(px2rem(-242px));\n        }\n      }\n\n      // Stretch scroll wrapper for primary sidebar\n      .md-sidebar__scrollwrap {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        margin: 0;\n        scroll-snap-type: none;\n        overflow: hidden;\n      }\n    }\n  }\n\n  // [screen +]: Show navigation as sidebar\n  @include break-from-device(screen) {\n    height: 0;\n\n    // [no-js]: Switch to native sticky behavior\n    .no-js & {\n      height: auto;\n    }\n  }\n\n  // Secondary sidebar with table of contents\n  &--secondary {\n    display: none;\n    order: 2;\n\n    // [tablet landscape +]: Show table of contents as sidebar\n    @include break-from-device(tablet landscape) {\n      height: 0;\n\n      // [no-js]: Switch to native sticky behavior\n      .no-js & {\n        height: auto;\n      }\n\n      // Sidebar is visible\n      &:not([hidden]) {\n        display: block;\n      }\n\n      // Ensure smooth scrolling on iOS\n      .md-sidebar__scrollwrap {\n        touch-action: pan-y;\n      }\n    }\n  }\n\n  // Sidebar scroll wrapper\n  &__scrollwrap {\n    margin: 0 px2rem(4px);\n    overflow-y: auto;\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container\n    // to the bottom if `scroll-snap-type` is set on the initial render. For\n    // this reason, we disable scroll snapping until this is resolved (#1667).\n    // scroll-snap-type: y mandatory;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n    // Sidebar scroll wrapper on hover\n    &:hover {\n      scrollbar-color: var(--md-accent-fg-color) transparent;\n    }\n\n    // Webkit scrollbar\n    &::-webkit-scrollbar {\n      width: px2rem(4px);\n      height: px2rem(4px);\n    }\n\n    // Webkit scrollbar thumb\n    &::-webkit-scrollbar-thumb {\n      background-color: var(--md-default-fg-color--lighter);\n\n      // Webkit scrollbar thumb on hover\n      &:hover {\n        background-color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n\n// [tablet -]: Show overlay on active drawer\n@include break-to-device(tablet) {\n\n  // Sidebar overlay\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: hsla(0, 0%, 0%, 0.54);\n    opacity: 0;\n    transition:\n      width     0ms 250ms,\n      height    0ms 250ms,\n      opacity 250ms;\n\n    // Show overlay when drawer is active\n    [data-md-toggle=\"drawer\"]:checked ~ & {\n      width: 100%;\n      height: 100%;\n      opacity: 1;\n      transition:\n        width     0ms,\n        height    0ms,\n        opacity 250ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Keyframes\n// ----------------------------------------------------------------------------\n\n// Show repository facts\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n\n  100% {\n    height: px2rem(13px);\n  }\n}\n\n// Show repository fact\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n\n  50% {\n    opacity: 0;\n  }\n\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n  --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n  --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n  --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Repository information\n.md-source {\n  display: block;\n  font-size: px2rem(13px);\n  line-height: 1.2;\n  white-space: nowrap;\n  // Hack: promote to own layer to reduce jitter\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n\n  // Repository information on hover\n  &:hover {\n    opacity: 0.7;\n  }\n\n  // Repository icon\n  &__icon {\n    display: inline-block;\n    width: px2rem(40px);\n    height: px2rem(48px);\n    vertical-align: middle;\n\n    // Align with margin only (as opposed to normal button alignment)\n    svg {\n      margin-top: px2rem(12px);\n      margin-left: px2rem(12px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(12px);\n        margin-left: initial;\n      }\n    }\n\n    // Adjust spacing if icon is present\n    + .md-source__repository {\n      margin-left: px2rem(-40px);\n      padding-left: px2rem(40px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(-40px);\n        margin-left: initial;\n        padding-right: px2rem(40px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Repository name\n  &__repository {\n    display: inline-block;\n    max-width: calc(100% - #{px2rem(24px)});\n    margin-left: px2rem(12px);\n    overflow: hidden;\n    text-overflow: ellipsis;\n    vertical-align: middle;\n  }\n\n  // Repository facts\n  &__facts {\n    margin: px2rem(2px) 0 0;\n    padding: 0;\n    overflow: hidden;\n    font-size: px2rem(11px);\n    list-style-type: none;\n    opacity: 0.75;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__facts--done 250ms ease-in;\n    }\n  }\n\n  // Repository fact\n  &__fact {\n    display: inline-block;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__fact--done 400ms ease-out;\n    }\n\n    // Repository fact icon\n    &::before {\n      display: inline-block;\n      width: px2rem(12px);\n      height: px2rem(12px);\n      margin-right: px2rem(2px);\n      vertical-align: text-top;\n      background-color: currentColor;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust spacing for repository fact icon\n    &:nth-child(1n+2)::before {\n      margin-left: px2rem(8px);\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: initial;\n      margin-left: px2rem(2px);\n\n      // Adjust spacing for repository fact icon\n      &:nth-child(1n+2)::before {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n\n    // Repository fact: version\n    &--version::before {\n      mask-image: var(--md-source-version-icon);\n    }\n\n    // Repository fact: stars\n    &--stars::before {\n      mask-image: var(--md-source-stars-icon);\n    }\n\n    // Repository fact: forks\n    &--forks::before {\n      mask-image: var(--md-source-forks-icon);\n    }\n\n    // Repository fact: repositories\n    &--repositories::before {\n      mask-image: var(--md-source-repositories-icon);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Navigation tabs\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n\n  // [print]: Hide tabs\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Hide tabs\n  @include break-to-device(tablet) {\n    display: none;\n  }\n\n  // Tabs in hidden state, i.e. when scrolling down\n  &[data-md-state=\"hidden\"] {\n    pointer-events: none;\n  }\n\n  // Navigation tabs list\n  &__list {\n    margin: 0;\n    margin-left: px2rem(4px);\n    padding: 0;\n    white-space: nowrap;\n    list-style: none;\n    contain: content;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(4px);\n      margin-left: initial;\n    }\n  }\n\n  // Navigation tabs item\n  &__item {\n    display: inline-block;\n    height: px2rem(48px);\n    padding-right: px2rem(12px);\n    padding-left: px2rem(12px);\n  }\n\n  // Navigation tabs link - could be defined as block elements and aligned via\n  // line height, but this would imply more repaints when scrolling\n  &__link {\n    display: block;\n    margin-top: px2rem(16px);\n    font-size: px2rem(14px);\n    // Hack: save a repaint when tabs are appearing on scrolling up\n    backface-visibility: hidden;\n    opacity: 0.7;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   250ms;\n\n    // Active link and link on focus/hover\n    &--active,\n    &:focus,\n    &:hover {\n      color: inherit;\n      opacity: 1;\n    }\n\n    // Delay transitions by a small amount\n    @for $i from 2 through 16 {\n      .md-tabs__item:nth-child(#{$i}) & {\n        transition-delay: 20ms * ($i - 1);\n      }\n    }\n\n    // Hide tabs upon scrolling - disable transition to minimizes repaints\n    // while scrolling down, while scrolling up seems to be okay\n    .md-tabs[data-md-state=\"hidden\"] & {\n      transform: translateY(50%);\n      opacity: 0;\n      transition:\n        transform 0ms 100ms,\n        opacity 100ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Back-to-top button\n.md-top {\n  position: sticky;\n  bottom: px2rem(8px);\n  z-index: 1;\n  float: right;\n  margin: px2rem(-56px) px2rem(8px) px2rem(8px);\n  padding: px2rem(8px);\n  color: var(--md-primary-bg-color);\n  background: var(--md-primary-fg-color);\n  border-radius: 100%;\n  outline: none;\n  box-shadow:\n    0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n    0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n  transform: translateY(0);\n  transition:\n    opacity          125ms,\n    transform        125ms cubic-bezier(0.4, 0, 0.2, 1),\n    background-color 125ms;\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    float: left;\n  }\n\n  // Back-to-top button in hidden state\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(px2rem(-4px));\n    opacity: 0;\n  }\n\n  // Back-to-top button on focus/hover\n  &:focus,\n  &:hover {\n    background: var(--md-accent-fg-color);\n    transform: scale(1.1);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Version selection\n.md-version {\n  flex-shrink: 0;\n  height: px2rem(48px);\n  font-size: px2rem(16px);\n\n  // Current selection\n  &__current {\n    position: relative;\n    // Hack: in general, we would use `vertical-align` to align the version at\n    // the bottom with the title, but since the list uses absolute positioning,\n    // this won't work consistently. Furthermore, we would need to use inline\n    // positioning to align the links, which looks jagged.\n    top: px2rem(1px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(28px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(28px);\n      margin-left: px2rem(8px);\n    }\n\n    // Version selection icon\n    &::after {\n      display: inline-block;\n      width: px2rem(8px);\n      height: px2rem(12px);\n      margin-left: px2rem(8px);\n      background-color: currentColor;\n      mask-image: var(--md-version-icon);\n      mask-repeat: no-repeat;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n  }\n\n  // Version selection list\n  &__list {\n    position: absolute;\n    top: px2rem(3px);\n    z-index: 1;\n    max-height: px2rem(36px);\n    margin: px2rem(4px) px2rem(16px);\n    padding: 0;\n    overflow: auto;\n    color: var(--md-default-fg-color);\n    list-style-type: none;\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    opacity: 0;\n    transition:\n      max-height 0ms 500ms,\n      opacity  250ms 250ms;\n    scroll-snap-type: y mandatory;\n\n    // List on focus/hover\n    &:focus-within,\n    &:hover {\n      max-height: px2rem(200px);\n      opacity: 1;\n      transition:\n        max-height 250ms,\n        opacity    250ms;\n    }\n  }\n\n  // Version selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Version selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    white-space: nowrap;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n/// Admonition flavours\n$admonitions: (\n  note:                       pencil $clr-blue-a200,\n  abstract summary tldr:      text-subject $clr-light-blue-a400,\n  info todo:                  information $clr-cyan-a700,\n  tip hint important:         fire $clr-teal-a700,\n  success check done:         check-circle $clr-green-a700,\n  question help faq:          help-circle $clr-light-green-a700,\n  warning caution attention:  alert $clr-orange-a400,\n  failure fail missing:       close-circle $clr-red-a200,\n  danger error:               flash-circle $clr-red-a400,\n  bug:                        bug $clr-pink-a400,\n  example:                    format-list-numbered $clr-deep-purple-a200,\n  quote cite:                 format-quote-close $clr-grey\n) !default;\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  @each $names, $props in $admonitions {\n    --md-admonition-icon--#{nth($names, 1)}:\n      svg-load(\"material/#{nth($props, 1)}.svg\");\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Admonition\n  .admonition {\n    margin: px2em(20px, 12.8px) 0;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    color: var(--md-admonition-fg-color);\n    font-size: px2rem(12.8px);\n    page-break-inside: avoid;\n    background-color: var(--md-admonition-bg-color);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.05);\n\n    // [print]: Omit shadow as it may lead to rendering errors\n    @media print {\n      box-shadow: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust vertical spacing for nested admonitions\n    .admonition {\n      margin-top: 1em;\n      margin-bottom: 1em;\n    }\n\n    // Adjust spacing for contained table wrappers\n    .md-typeset__scrollwrap {\n      margin: 1em px2rem(-12px);\n    }\n\n    // Adjust spacing for contained tables\n    .md-typeset__table {\n      padding: 0 px2rem(12px);\n    }\n\n    // Adjust spacing for single-child tabbed block container\n    > .tabbed-set:only-child {\n      margin-top: 0;\n    }\n\n    // Adjust spacing on last child\n    html & > :last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Admonition title\n  .admonition-title {\n    position: relative;\n    margin: 0 px2rem(-12px) 0 px2rem(-16px);\n    padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);\n    font-weight: 700;\n    background-color: transparentize($clr-blue-a200, 0.9);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin: 0 px2rem(-16px) 0 px2rem(-12px);\n      padding: px2rem(8px) px2rem(40px) px2rem(8px) px2rem(12px);\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for title-only admonitions\n    html &:last-child {\n      margin-bottom: 0;\n    }\n\n    // Admonition icon\n    &::before {\n      position: absolute;\n      left: px2rem(12px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: $clr-blue-a200;\n      mask-image: var(--md-admonition-icon--note);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(12px);\n        left: initial;\n      }\n    }\n\n    // Adjust spacing on last tabbed block container child - if the tabbed\n    // block container is the sole child, it looks better to omit the margin\n    + .tabbed-set:last-child {\n      margin-top: 0;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: flavours\n// ----------------------------------------------------------------------------\n\n@each $names, $props in $admonitions {\n  $name: nth($names, 1);\n  $tint: nth($props, 2);\n\n  // Admonition flavour\n  .md-typeset .admonition.#{$name} {\n    border-color: $tint;\n  }\n\n  // Admonition flavour title\n  .md-typeset .#{$name} > .admonition-title {\n    background-color: transparentize($tint, 0.9);\n    border-color: $tint;\n\n    // Admonition icon\n    &::before {\n      background-color: $tint;\n      mask-image: var(--md-admonition-icon--#{$name});\n      mask-repeat: no-repeat;\n      mask-size: contain;\n    }\n  }\n\n  // Define synonyms for flavours\n  @if length($names) > 1 {\n    @for $n from 2 through length($names) {\n      .#{nth($names, $n)} {\n        @extend .#{$name};\n      }\n    }\n  }\n}\n","// ==========================================================================\n//\n// Name:        UI Color Palette\n// Description: The color palette of material design.\n// Version:     2.3.1\n//\n// Author:      Denis Malinochkin\n// Git:         https://github.com/mrmlnc/material-color\n//\n// twitter:     @mrmlnc\n//\n// ==========================================================================\n\n\n//\n// List of base colors\n//\n\n// $clr-red\n// $clr-pink\n// $clr-purple\n// $clr-deep-purple\n// $clr-indigo\n// $clr-blue\n// $clr-light-blue\n// $clr-cyan\n// $clr-teal\n// $clr-green\n// $clr-light-green\n// $clr-lime\n// $clr-yellow\n// $clr-amber\n// $clr-orange\n// $clr-deep-orange\n// $clr-brown\n// $clr-grey\n// $clr-blue-grey\n// $clr-black\n// $clr-white\n\n\n//\n// Red\n//\n\n$clr-red-list: (\n  \"base\": #f44336,\n  \"50\":   #ffebee,\n  \"100\":  #ffcdd2,\n  \"200\":  #ef9a9a,\n  \"300\":  #e57373,\n  \"400\":  #ef5350,\n  \"500\":  #f44336,\n  \"600\":  #e53935,\n  \"700\":  #d32f2f,\n  \"800\":  #c62828,\n  \"900\":  #b71c1c,\n  \"a100\": #ff8a80,\n  \"a200\": #ff5252,\n  \"a400\": #ff1744,\n  \"a700\": #d50000\n);\n\n$clr-red:      map-get($clr-red-list, \"base\");\n\n$clr-red-50:   map-get($clr-red-list, \"50\");\n$clr-red-100:  map-get($clr-red-list, \"100\");\n$clr-red-200:  map-get($clr-red-list, \"200\");\n$clr-red-300:  map-get($clr-red-list, \"300\");\n$clr-red-400:  map-get($clr-red-list, \"400\");\n$clr-red-500:  map-get($clr-red-list, \"500\");\n$clr-red-600:  map-get($clr-red-list, \"600\");\n$clr-red-700:  map-get($clr-red-list, \"700\");\n$clr-red-800:  map-get($clr-red-list, \"800\");\n$clr-red-900:  map-get($clr-red-list, \"900\");\n$clr-red-a100: map-get($clr-red-list, \"a100\");\n$clr-red-a200: map-get($clr-red-list, \"a200\");\n$clr-red-a400: map-get($clr-red-list, \"a400\");\n$clr-red-a700: map-get($clr-red-list, \"a700\");\n\n\n//\n// Pink\n//\n\n$clr-pink-list: (\n  \"base\": #e91e63,\n  \"50\":   #fce4ec,\n  \"100\":  #f8bbd0,\n  \"200\":  #f48fb1,\n  \"300\":  #f06292,\n  \"400\":  #ec407a,\n  \"500\":  #e91e63,\n  \"600\":  #d81b60,\n  \"700\":  #c2185b,\n  \"800\":  #ad1457,\n  \"900\":  #880e4f,\n  \"a100\": #ff80ab,\n  \"a200\": #ff4081,\n  \"a400\": #f50057,\n  \"a700\": #c51162\n);\n\n$clr-pink:      map-get($clr-pink-list, \"base\");\n\n$clr-pink-50:   map-get($clr-pink-list, \"50\");\n$clr-pink-100:  map-get($clr-pink-list, \"100\");\n$clr-pink-200:  map-get($clr-pink-list, \"200\");\n$clr-pink-300:  map-get($clr-pink-list, \"300\");\n$clr-pink-400:  map-get($clr-pink-list, \"400\");\n$clr-pink-500:  map-get($clr-pink-list, \"500\");\n$clr-pink-600:  map-get($clr-pink-list, \"600\");\n$clr-pink-700:  map-get($clr-pink-list, \"700\");\n$clr-pink-800:  map-get($clr-pink-list, \"800\");\n$clr-pink-900:  map-get($clr-pink-list, \"900\");\n$clr-pink-a100: map-get($clr-pink-list, \"a100\");\n$clr-pink-a200: map-get($clr-pink-list, \"a200\");\n$clr-pink-a400: map-get($clr-pink-list, \"a400\");\n$clr-pink-a700: map-get($clr-pink-list, \"a700\");\n\n\n//\n// Purple\n//\n\n$clr-purple-list: (\n  \"base\": #9c27b0,\n  \"50\":   #f3e5f5,\n  \"100\":  #e1bee7,\n  \"200\":  #ce93d8,\n  \"300\":  #ba68c8,\n  \"400\":  #ab47bc,\n  \"500\":  #9c27b0,\n  \"600\":  #8e24aa,\n  \"700\":  #7b1fa2,\n  \"800\":  #6a1b9a,\n  \"900\":  #4a148c,\n  \"a100\": #ea80fc,\n  \"a200\": #e040fb,\n  \"a400\": #d500f9,\n  \"a700\": #aa00ff\n);\n\n$clr-purple:      map-get($clr-purple-list, \"base\");\n\n$clr-purple-50:   map-get($clr-purple-list, \"50\");\n$clr-purple-100:  map-get($clr-purple-list, \"100\");\n$clr-purple-200:  map-get($clr-purple-list, \"200\");\n$clr-purple-300:  map-get($clr-purple-list, \"300\");\n$clr-purple-400:  map-get($clr-purple-list, \"400\");\n$clr-purple-500:  map-get($clr-purple-list, \"500\");\n$clr-purple-600:  map-get($clr-purple-list, \"600\");\n$clr-purple-700:  map-get($clr-purple-list, \"700\");\n$clr-purple-800:  map-get($clr-purple-list, \"800\");\n$clr-purple-900:  map-get($clr-purple-list, \"900\");\n$clr-purple-a100: map-get($clr-purple-list, \"a100\");\n$clr-purple-a200: map-get($clr-purple-list, \"a200\");\n$clr-purple-a400: map-get($clr-purple-list, \"a400\");\n$clr-purple-a700: map-get($clr-purple-list, \"a700\");\n\n\n//\n// Deep purple\n//\n\n$clr-deep-purple-list: (\n  \"base\": #673ab7,\n  \"50\":   #ede7f6,\n  \"100\":  #d1c4e9,\n  \"200\":  #b39ddb,\n  \"300\":  #9575cd,\n  \"400\":  #7e57c2,\n  \"500\":  #673ab7,\n  \"600\":  #5e35b1,\n  \"700\":  #512da8,\n  \"800\":  #4527a0,\n  \"900\":  #311b92,\n  \"a100\": #b388ff,\n  \"a200\": #7c4dff,\n  \"a400\": #651fff,\n  \"a700\": #6200ea\n);\n\n$clr-deep-purple:      map-get($clr-deep-purple-list, \"base\");\n\n$clr-deep-purple-50:   map-get($clr-deep-purple-list, \"50\");\n$clr-deep-purple-100:  map-get($clr-deep-purple-list, \"100\");\n$clr-deep-purple-200:  map-get($clr-deep-purple-list, \"200\");\n$clr-deep-purple-300:  map-get($clr-deep-purple-list, \"300\");\n$clr-deep-purple-400:  map-get($clr-deep-purple-list, \"400\");\n$clr-deep-purple-500:  map-get($clr-deep-purple-list, \"500\");\n$clr-deep-purple-600:  map-get($clr-deep-purple-list, \"600\");\n$clr-deep-purple-700:  map-get($clr-deep-purple-list, \"700\");\n$clr-deep-purple-800:  map-get($clr-deep-purple-list, \"800\");\n$clr-deep-purple-900:  map-get($clr-deep-purple-list, \"900\");\n$clr-deep-purple-a100: map-get($clr-deep-purple-list, \"a100\");\n$clr-deep-purple-a200: map-get($clr-deep-purple-list, \"a200\");\n$clr-deep-purple-a400: map-get($clr-deep-purple-list, \"a400\");\n$clr-deep-purple-a700: map-get($clr-deep-purple-list, \"a700\");\n\n\n//\n// Indigo\n//\n\n$clr-indigo-list: (\n  \"base\": #3f51b5,\n  \"50\":   #e8eaf6,\n  \"100\":  #c5cae9,\n  \"200\":  #9fa8da,\n  \"300\":  #7986cb,\n  \"400\":  #5c6bc0,\n  \"500\":  #3f51b5,\n  \"600\":  #3949ab,\n  \"700\":  #303f9f,\n  \"800\":  #283593,\n  \"900\":  #1a237e,\n  \"a100\": #8c9eff,\n  \"a200\": #536dfe,\n  \"a400\": #3d5afe,\n  \"a700\": #304ffe\n);\n\n$clr-indigo:      map-get($clr-indigo-list, \"base\");\n\n$clr-indigo-50:   map-get($clr-indigo-list, \"50\");\n$clr-indigo-100:  map-get($clr-indigo-list, \"100\");\n$clr-indigo-200:  map-get($clr-indigo-list, \"200\");\n$clr-indigo-300:  map-get($clr-indigo-list, \"300\");\n$clr-indigo-400:  map-get($clr-indigo-list, \"400\");\n$clr-indigo-500:  map-get($clr-indigo-list, \"500\");\n$clr-indigo-600:  map-get($clr-indigo-list, \"600\");\n$clr-indigo-700:  map-get($clr-indigo-list, \"700\");\n$clr-indigo-800:  map-get($clr-indigo-list, \"800\");\n$clr-indigo-900:  map-get($clr-indigo-list, \"900\");\n$clr-indigo-a100: map-get($clr-indigo-list, \"a100\");\n$clr-indigo-a200: map-get($clr-indigo-list, \"a200\");\n$clr-indigo-a400: map-get($clr-indigo-list, \"a400\");\n$clr-indigo-a700: map-get($clr-indigo-list, \"a700\");\n\n\n//\n// Blue\n//\n\n$clr-blue-list: (\n  \"base\": #2196f3,\n  \"50\":   #e3f2fd,\n  \"100\":  #bbdefb,\n  \"200\":  #90caf9,\n  \"300\":  #64b5f6,\n  \"400\":  #42a5f5,\n  \"500\":  #2196f3,\n  \"600\":  #1e88e5,\n  \"700\":  #1976d2,\n  \"800\":  #1565c0,\n  \"900\":  #0d47a1,\n  \"a100\": #82b1ff,\n  \"a200\": #448aff,\n  \"a400\": #2979ff,\n  \"a700\": #2962ff\n);\n\n$clr-blue:      map-get($clr-blue-list, \"base\");\n\n$clr-blue-50:   map-get($clr-blue-list, \"50\");\n$clr-blue-100:  map-get($clr-blue-list, \"100\");\n$clr-blue-200:  map-get($clr-blue-list, \"200\");\n$clr-blue-300:  map-get($clr-blue-list, \"300\");\n$clr-blue-400:  map-get($clr-blue-list, \"400\");\n$clr-blue-500:  map-get($clr-blue-list, \"500\");\n$clr-blue-600:  map-get($clr-blue-list, \"600\");\n$clr-blue-700:  map-get($clr-blue-list, \"700\");\n$clr-blue-800:  map-get($clr-blue-list, \"800\");\n$clr-blue-900:  map-get($clr-blue-list, \"900\");\n$clr-blue-a100: map-get($clr-blue-list, \"a100\");\n$clr-blue-a200: map-get($clr-blue-list, \"a200\");\n$clr-blue-a400: map-get($clr-blue-list, \"a400\");\n$clr-blue-a700: map-get($clr-blue-list, \"a700\");\n\n\n//\n// Light Blue\n//\n\n$clr-light-blue-list: (\n  \"base\": #03a9f4,\n  \"50\":   #e1f5fe,\n  \"100\":  #b3e5fc,\n  \"200\":  #81d4fa,\n  \"300\":  #4fc3f7,\n  \"400\":  #29b6f6,\n  \"500\":  #03a9f4,\n  \"600\":  #039be5,\n  \"700\":  #0288d1,\n  \"800\":  #0277bd,\n  \"900\":  #01579b,\n  \"a100\": #80d8ff,\n  \"a200\": #40c4ff,\n  \"a400\": #00b0ff,\n  \"a700\": #0091ea\n);\n\n$clr-light-blue:      map-get($clr-light-blue-list, \"base\");\n\n$clr-light-blue-50:   map-get($clr-light-blue-list, \"50\");\n$clr-light-blue-100:  map-get($clr-light-blue-list, \"100\");\n$clr-light-blue-200:  map-get($clr-light-blue-list, \"200\");\n$clr-light-blue-300:  map-get($clr-light-blue-list, \"300\");\n$clr-light-blue-400:  map-get($clr-light-blue-list, \"400\");\n$clr-light-blue-500:  map-get($clr-light-blue-list, \"500\");\n$clr-light-blue-600:  map-get($clr-light-blue-list, \"600\");\n$clr-light-blue-700:  map-get($clr-light-blue-list, \"700\");\n$clr-light-blue-800:  map-get($clr-light-blue-list, \"800\");\n$clr-light-blue-900:  map-get($clr-light-blue-list, \"900\");\n$clr-light-blue-a100: map-get($clr-light-blue-list, \"a100\");\n$clr-light-blue-a200: map-get($clr-light-blue-list, \"a200\");\n$clr-light-blue-a400: map-get($clr-light-blue-list, \"a400\");\n$clr-light-blue-a700: map-get($clr-light-blue-list, \"a700\");\n\n\n//\n// Cyan\n//\n\n$clr-cyan-list: (\n  \"base\": #00bcd4,\n  \"50\":   #e0f7fa,\n  \"100\":  #b2ebf2,\n  \"200\":  #80deea,\n  \"300\":  #4dd0e1,\n  \"400\":  #26c6da,\n  \"500\":  #00bcd4,\n  \"600\":  #00acc1,\n  \"700\":  #0097a7,\n  \"800\":  #00838f,\n  \"900\":  #006064,\n  \"a100\": #84ffff,\n  \"a200\": #18ffff,\n  \"a400\": #00e5ff,\n  \"a700\": #00b8d4\n);\n\n$clr-cyan:      map-get($clr-cyan-list, \"base\");\n\n$clr-cyan-50:   map-get($clr-cyan-list, \"50\");\n$clr-cyan-100:  map-get($clr-cyan-list, \"100\");\n$clr-cyan-200:  map-get($clr-cyan-list, \"200\");\n$clr-cyan-300:  map-get($clr-cyan-list, \"300\");\n$clr-cyan-400:  map-get($clr-cyan-list, \"400\");\n$clr-cyan-500:  map-get($clr-cyan-list, \"500\");\n$clr-cyan-600:  map-get($clr-cyan-list, \"600\");\n$clr-cyan-700:  map-get($clr-cyan-list, \"700\");\n$clr-cyan-800:  map-get($clr-cyan-list, \"800\");\n$clr-cyan-900:  map-get($clr-cyan-list, \"900\");\n$clr-cyan-a100: map-get($clr-cyan-list, \"a100\");\n$clr-cyan-a200: map-get($clr-cyan-list, \"a200\");\n$clr-cyan-a400: map-get($clr-cyan-list, \"a400\");\n$clr-cyan-a700: map-get($clr-cyan-list, \"a700\");\n\n\n//\n// Teal\n//\n\n$clr-teal-list: (\n  \"base\": #009688,\n  \"50\":   #e0f2f1,\n  \"100\":  #b2dfdb,\n  \"200\":  #80cbc4,\n  \"300\":  #4db6ac,\n  \"400\":  #26a69a,\n  \"500\":  #009688,\n  \"600\":  #00897b,\n  \"700\":  #00796b,\n  \"800\":  #00695c,\n  \"900\":  #004d40,\n  \"a100\": #a7ffeb,\n  \"a200\": #64ffda,\n  \"a400\": #1de9b6,\n  \"a700\": #00bfa5\n);\n\n$clr-teal:      map-get($clr-teal-list, \"base\");\n\n$clr-teal-50:   map-get($clr-teal-list, \"50\");\n$clr-teal-100:  map-get($clr-teal-list, \"100\");\n$clr-teal-200:  map-get($clr-teal-list, \"200\");\n$clr-teal-300:  map-get($clr-teal-list, \"300\");\n$clr-teal-400:  map-get($clr-teal-list, \"400\");\n$clr-teal-500:  map-get($clr-teal-list, \"500\");\n$clr-teal-600:  map-get($clr-teal-list, \"600\");\n$clr-teal-700:  map-get($clr-teal-list, \"700\");\n$clr-teal-800:  map-get($clr-teal-list, \"800\");\n$clr-teal-900:  map-get($clr-teal-list, \"900\");\n$clr-teal-a100: map-get($clr-teal-list, \"a100\");\n$clr-teal-a200: map-get($clr-teal-list, \"a200\");\n$clr-teal-a400: map-get($clr-teal-list, \"a400\");\n$clr-teal-a700: map-get($clr-teal-list, \"a700\");\n\n\n//\n// Green\n//\n\n$clr-green-list: (\n  \"base\": #4caf50,\n  \"50\":   #e8f5e9,\n  \"100\":  #c8e6c9,\n  \"200\":  #a5d6a7,\n  \"300\":  #81c784,\n  \"400\":  #66bb6a,\n  \"500\":  #4caf50,\n  \"600\":  #43a047,\n  \"700\":  #388e3c,\n  \"800\":  #2e7d32,\n  \"900\":  #1b5e20,\n  \"a100\": #b9f6ca,\n  \"a200\": #69f0ae,\n  \"a400\": #00e676,\n  \"a700\": #00c853\n);\n\n$clr-green:      map-get($clr-green-list, \"base\");\n\n$clr-green-50:   map-get($clr-green-list, \"50\");\n$clr-green-100:  map-get($clr-green-list, \"100\");\n$clr-green-200:  map-get($clr-green-list, \"200\");\n$clr-green-300:  map-get($clr-green-list, \"300\");\n$clr-green-400:  map-get($clr-green-list, \"400\");\n$clr-green-500:  map-get($clr-green-list, \"500\");\n$clr-green-600:  map-get($clr-green-list, \"600\");\n$clr-green-700:  map-get($clr-green-list, \"700\");\n$clr-green-800:  map-get($clr-green-list, \"800\");\n$clr-green-900:  map-get($clr-green-list, \"900\");\n$clr-green-a100: map-get($clr-green-list, \"a100\");\n$clr-green-a200: map-get($clr-green-list, \"a200\");\n$clr-green-a400: map-get($clr-green-list, \"a400\");\n$clr-green-a700: map-get($clr-green-list, \"a700\");\n\n\n//\n// Light green\n//\n\n$clr-light-green-list: (\n  \"base\": #8bc34a,\n  \"50\":   #f1f8e9,\n  \"100\":  #dcedc8,\n  \"200\":  #c5e1a5,\n  \"300\":  #aed581,\n  \"400\":  #9ccc65,\n  \"500\":  #8bc34a,\n  \"600\":  #7cb342,\n  \"700\":  #689f38,\n  \"800\":  #558b2f,\n  \"900\":  #33691e,\n  \"a100\": #ccff90,\n  \"a200\": #b2ff59,\n  \"a400\": #76ff03,\n  \"a700\": #64dd17\n);\n\n$clr-light-green:      map-get($clr-light-green-list, \"base\");\n\n$clr-light-green-50:   map-get($clr-light-green-list, \"50\");\n$clr-light-green-100:  map-get($clr-light-green-list, \"100\");\n$clr-light-green-200:  map-get($clr-light-green-list, \"200\");\n$clr-light-green-300:  map-get($clr-light-green-list, \"300\");\n$clr-light-green-400:  map-get($clr-light-green-list, \"400\");\n$clr-light-green-500:  map-get($clr-light-green-list, \"500\");\n$clr-light-green-600:  map-get($clr-light-green-list, \"600\");\n$clr-light-green-700:  map-get($clr-light-green-list, \"700\");\n$clr-light-green-800:  map-get($clr-light-green-list, \"800\");\n$clr-light-green-900:  map-get($clr-light-green-list, \"900\");\n$clr-light-green-a100: map-get($clr-light-green-list, \"a100\");\n$clr-light-green-a200: map-get($clr-light-green-list, \"a200\");\n$clr-light-green-a400: map-get($clr-light-green-list, \"a400\");\n$clr-light-green-a700: map-get($clr-light-green-list, \"a700\");\n\n\n//\n// Lime\n//\n\n$clr-lime-list: (\n  \"base\": #cddc39,\n  \"50\":   #f9fbe7,\n  \"100\":  #f0f4c3,\n  \"200\":  #e6ee9c,\n  \"300\":  #dce775,\n  \"400\":  #d4e157,\n  \"500\":  #cddc39,\n  \"600\":  #c0ca33,\n  \"700\":  #afb42b,\n  \"800\":  #9e9d24,\n  \"900\":  #827717,\n  \"a100\": #f4ff81,\n  \"a200\": #eeff41,\n  \"a400\": #c6ff00,\n  \"a700\": #aeea00\n);\n\n$clr-lime:      map-get($clr-lime-list, \"base\");\n\n$clr-lime-50:   map-get($clr-lime-list, \"50\");\n$clr-lime-100:  map-get($clr-lime-list, \"100\");\n$clr-lime-200:  map-get($clr-lime-list, \"200\");\n$clr-lime-300:  map-get($clr-lime-list, \"300\");\n$clr-lime-400:  map-get($clr-lime-list, \"400\");\n$clr-lime-500:  map-get($clr-lime-list, \"500\");\n$clr-lime-600:  map-get($clr-lime-list, \"600\");\n$clr-lime-700:  map-get($clr-lime-list, \"700\");\n$clr-lime-800:  map-get($clr-lime-list, \"800\");\n$clr-lime-900:  map-get($clr-lime-list, \"900\");\n$clr-lime-a100: map-get($clr-lime-list, \"a100\");\n$clr-lime-a200: map-get($clr-lime-list, \"a200\");\n$clr-lime-a400: map-get($clr-lime-list, \"a400\");\n$clr-lime-a700: map-get($clr-lime-list, \"a700\");\n\n\n//\n// Yellow\n//\n\n$clr-yellow-list: (\n  \"base\": #ffeb3b,\n  \"50\":   #fffde7,\n  \"100\":  #fff9c4,\n  \"200\":  #fff59d,\n  \"300\":  #fff176,\n  \"400\":  #ffee58,\n  \"500\":  #ffeb3b,\n  \"600\":  #fdd835,\n  \"700\":  #fbc02d,\n  \"800\":  #f9a825,\n  \"900\":  #f57f17,\n  \"a100\": #ffff8d,\n  \"a200\": #ffff00,\n  \"a400\": #ffea00,\n  \"a700\": #ffd600\n);\n\n$clr-yellow:      map-get($clr-yellow-list, \"base\");\n\n$clr-yellow-50:   map-get($clr-yellow-list, \"50\");\n$clr-yellow-100:  map-get($clr-yellow-list, \"100\");\n$clr-yellow-200:  map-get($clr-yellow-list, \"200\");\n$clr-yellow-300:  map-get($clr-yellow-list, \"300\");\n$clr-yellow-400:  map-get($clr-yellow-list, \"400\");\n$clr-yellow-500:  map-get($clr-yellow-list, \"500\");\n$clr-yellow-600:  map-get($clr-yellow-list, \"600\");\n$clr-yellow-700:  map-get($clr-yellow-list, \"700\");\n$clr-yellow-800:  map-get($clr-yellow-list, \"800\");\n$clr-yellow-900:  map-get($clr-yellow-list, \"900\");\n$clr-yellow-a100: map-get($clr-yellow-list, \"a100\");\n$clr-yellow-a200: map-get($clr-yellow-list, \"a200\");\n$clr-yellow-a400: map-get($clr-yellow-list, \"a400\");\n$clr-yellow-a700: map-get($clr-yellow-list, \"a700\");\n\n\n//\n// amber\n//\n\n$clr-amber-list: (\n  \"base\": #ffc107,\n  \"50\":   #fff8e1,\n  \"100\":  #ffecb3,\n  \"200\":  #ffe082,\n  \"300\":  #ffd54f,\n  \"400\":  #ffca28,\n  \"500\":  #ffc107,\n  \"600\":  #ffb300,\n  \"700\":  #ffa000,\n  \"800\":  #ff8f00,\n  \"900\":  #ff6f00,\n  \"a100\": #ffe57f,\n  \"a200\": #ffd740,\n  \"a400\": #ffc400,\n  \"a700\": #ffab00\n);\n\n$clr-amber:      map-get($clr-amber-list, \"base\");\n\n$clr-amber-50:   map-get($clr-amber-list, \"50\");\n$clr-amber-100:  map-get($clr-amber-list, \"100\");\n$clr-amber-200:  map-get($clr-amber-list, \"200\");\n$clr-amber-300:  map-get($clr-amber-list, \"300\");\n$clr-amber-400:  map-get($clr-amber-list, \"400\");\n$clr-amber-500:  map-get($clr-amber-list, \"500\");\n$clr-amber-600:  map-get($clr-amber-list, \"600\");\n$clr-amber-700:  map-get($clr-amber-list, \"700\");\n$clr-amber-800:  map-get($clr-amber-list, \"800\");\n$clr-amber-900:  map-get($clr-amber-list, \"900\");\n$clr-amber-a100: map-get($clr-amber-list, \"a100\");\n$clr-amber-a200: map-get($clr-amber-list, \"a200\");\n$clr-amber-a400: map-get($clr-amber-list, \"a400\");\n$clr-amber-a700: map-get($clr-amber-list, \"a700\");\n\n\n//\n// Orange\n//\n\n$clr-orange-list: (\n  \"base\": #ff9800,\n  \"50\":   #fff3e0,\n  \"100\":  #ffe0b2,\n  \"200\":  #ffcc80,\n  \"300\":  #ffb74d,\n  \"400\":  #ffa726,\n  \"500\":  #ff9800,\n  \"600\":  #fb8c00,\n  \"700\":  #f57c00,\n  \"800\":  #ef6c00,\n  \"900\":  #e65100,\n  \"a100\": #ffd180,\n  \"a200\": #ffab40,\n  \"a400\": #ff9100,\n  \"a700\": #ff6d00\n);\n\n$clr-orange:      map-get($clr-orange-list, \"base\");\n\n$clr-orange-50:   map-get($clr-orange-list, \"50\");\n$clr-orange-100:  map-get($clr-orange-list, \"100\");\n$clr-orange-200:  map-get($clr-orange-list, \"200\");\n$clr-orange-300:  map-get($clr-orange-list, \"300\");\n$clr-orange-400:  map-get($clr-orange-list, \"400\");\n$clr-orange-500:  map-get($clr-orange-list, \"500\");\n$clr-orange-600:  map-get($clr-orange-list, \"600\");\n$clr-orange-700:  map-get($clr-orange-list, \"700\");\n$clr-orange-800:  map-get($clr-orange-list, \"800\");\n$clr-orange-900:  map-get($clr-orange-list, \"900\");\n$clr-orange-a100: map-get($clr-orange-list, \"a100\");\n$clr-orange-a200: map-get($clr-orange-list, \"a200\");\n$clr-orange-a400: map-get($clr-orange-list, \"a400\");\n$clr-orange-a700: map-get($clr-orange-list, \"a700\");\n\n\n//\n// Deep orange\n//\n\n$clr-deep-orange-list: (\n  \"base\": #ff5722,\n  \"50\":   #fbe9e7,\n  \"100\":  #ffccbc,\n  \"200\":  #ffab91,\n  \"300\":  #ff8a65,\n  \"400\":  #ff7043,\n  \"500\":  #ff5722,\n  \"600\":  #f4511e,\n  \"700\":  #e64a19,\n  \"800\":  #d84315,\n  \"900\":  #bf360c,\n  \"a100\": #ff9e80,\n  \"a200\": #ff6e40,\n  \"a400\": #ff3d00,\n  \"a700\": #dd2c00\n);\n\n$clr-deep-orange:      map-get($clr-deep-orange-list, \"base\");\n\n$clr-deep-orange-50:   map-get($clr-deep-orange-list, \"50\");\n$clr-deep-orange-100:  map-get($clr-deep-orange-list, \"100\");\n$clr-deep-orange-200:  map-get($clr-deep-orange-list, \"200\");\n$clr-deep-orange-300:  map-get($clr-deep-orange-list, \"300\");\n$clr-deep-orange-400:  map-get($clr-deep-orange-list, \"400\");\n$clr-deep-orange-500:  map-get($clr-deep-orange-list, \"500\");\n$clr-deep-orange-600:  map-get($clr-deep-orange-list, \"600\");\n$clr-deep-orange-700:  map-get($clr-deep-orange-list, \"700\");\n$clr-deep-orange-800:  map-get($clr-deep-orange-list, \"800\");\n$clr-deep-orange-900:  map-get($clr-deep-orange-list, \"900\");\n$clr-deep-orange-a100: map-get($clr-deep-orange-list, \"a100\");\n$clr-deep-orange-a200: map-get($clr-deep-orange-list, \"a200\");\n$clr-deep-orange-a400: map-get($clr-deep-orange-list, \"a400\");\n$clr-deep-orange-a700: map-get($clr-deep-orange-list, \"a700\");\n\n\n//\n// Brown\n//\n\n$clr-brown-list: (\n  \"base\": #795548,\n  \"50\":   #efebe9,\n  \"100\":  #d7ccc8,\n  \"200\":  #bcaaa4,\n  \"300\":  #a1887f,\n  \"400\":  #8d6e63,\n  \"500\":  #795548,\n  \"600\":  #6d4c41,\n  \"700\":  #5d4037,\n  \"800\":  #4e342e,\n  \"900\":  #3e2723,\n);\n\n$clr-brown:     map-get($clr-brown-list, \"base\");\n\n$clr-brown-50:  map-get($clr-brown-list, \"50\");\n$clr-brown-100: map-get($clr-brown-list, \"100\");\n$clr-brown-200: map-get($clr-brown-list, \"200\");\n$clr-brown-300: map-get($clr-brown-list, \"300\");\n$clr-brown-400: map-get($clr-brown-list, \"400\");\n$clr-brown-500: map-get($clr-brown-list, \"500\");\n$clr-brown-600: map-get($clr-brown-list, \"600\");\n$clr-brown-700: map-get($clr-brown-list, \"700\");\n$clr-brown-800: map-get($clr-brown-list, \"800\");\n$clr-brown-900: map-get($clr-brown-list, \"900\");\n\n\n//\n// Grey\n//\n\n$clr-grey-list: (\n  \"base\": #9e9e9e,\n  \"50\":   #fafafa,\n  \"100\":  #f5f5f5,\n  \"200\":  #eeeeee,\n  \"300\":  #e0e0e0,\n  \"400\":  #bdbdbd,\n  \"500\":  #9e9e9e,\n  \"600\":  #757575,\n  \"700\":  #616161,\n  \"800\":  #424242,\n  \"900\":  #212121,\n);\n\n$clr-grey:     map-get($clr-grey-list, \"base\");\n\n$clr-grey-50:  map-get($clr-grey-list, \"50\");\n$clr-grey-100: map-get($clr-grey-list, \"100\");\n$clr-grey-200: map-get($clr-grey-list, \"200\");\n$clr-grey-300: map-get($clr-grey-list, \"300\");\n$clr-grey-400: map-get($clr-grey-list, \"400\");\n$clr-grey-500: map-get($clr-grey-list, \"500\");\n$clr-grey-600: map-get($clr-grey-list, \"600\");\n$clr-grey-700: map-get($clr-grey-list, \"700\");\n$clr-grey-800: map-get($clr-grey-list, \"800\");\n$clr-grey-900: map-get($clr-grey-list, \"900\");\n\n\n//\n// Blue grey\n//\n\n$clr-blue-grey-list: (\n  \"base\": #607d8b,\n  \"50\":   #eceff1,\n  \"100\":  #cfd8dc,\n  \"200\":  #b0bec5,\n  \"300\":  #90a4ae,\n  \"400\":  #78909c,\n  \"500\":  #607d8b,\n  \"600\":  #546e7a,\n  \"700\":  #455a64,\n  \"800\":  #37474f,\n  \"900\":  #263238,\n);\n\n$clr-blue-grey:     map-get($clr-blue-grey-list, \"base\");\n\n$clr-blue-grey-50:  map-get($clr-blue-grey-list, \"50\");\n$clr-blue-grey-100: map-get($clr-blue-grey-list, \"100\");\n$clr-blue-grey-200: map-get($clr-blue-grey-list, \"200\");\n$clr-blue-grey-300: map-get($clr-blue-grey-list, \"300\");\n$clr-blue-grey-400: map-get($clr-blue-grey-list, \"400\");\n$clr-blue-grey-500: map-get($clr-blue-grey-list, \"500\");\n$clr-blue-grey-600: map-get($clr-blue-grey-list, \"600\");\n$clr-blue-grey-700: map-get($clr-blue-grey-list, \"700\");\n$clr-blue-grey-800: map-get($clr-blue-grey-list, \"800\");\n$clr-blue-grey-900: map-get($clr-blue-grey-list, \"900\");\n\n\n//\n// Black\n//\n\n$clr-black-list: (\n  \"base\": #000\n);\n\n$clr-black: map-get($clr-black-list, \"base\");\n\n\n//\n// White\n//\n\n$clr-white-list: (\n  \"base\": #fff\n);\n\n$clr-white: map-get($clr-white-list, \"base\");\n\n\n//\n// List for all Colors for looping\n//\n\n$clr-list-all: (\n  \"red\":         $clr-red-list,\n  \"pink\":        $clr-pink-list,\n  \"purple\":      $clr-purple-list,\n  \"deep-purple\": $clr-deep-purple-list,\n  \"indigo\":      $clr-indigo-list,\n  \"blue\":        $clr-blue-list,\n  \"light-blue\":  $clr-light-blue-list,\n  \"cyan\":        $clr-cyan-list,\n  \"teal\":        $clr-teal-list,\n  \"green\":       $clr-green-list,\n  \"light-green\": $clr-light-green-list,\n  \"lime\":        $clr-lime-list,\n  \"yellow\":      $clr-yellow-list,\n  \"amber\":       $clr-amber-list,\n  \"orange\":      $clr-orange-list,\n  \"deep-orange\": $clr-deep-orange-list,\n  \"brown\":       $clr-brown-list,\n  \"grey\":        $clr-grey-list,\n  \"blue-grey\":   $clr-blue-grey-list,\n  \"black\":       $clr-black-list,\n  \"white\":       $clr-white-list\n);\n\n\n//\n// Typography\n//\n\n$clr-ui-display-4: $clr-grey-600;\n$clr-ui-display-3: $clr-grey-600;\n$clr-ui-display-2: $clr-grey-600;\n$clr-ui-display-1: $clr-grey-600;\n$clr-ui-headline:  $clr-grey-900;\n$clr-ui-title:     $clr-grey-900;\n$clr-ui-subhead-1: $clr-grey-900;\n$clr-ui-body-2:    $clr-grey-900;\n$clr-ui-body-1:    $clr-grey-900;\n$clr-ui-caption:   $clr-grey-600;\n$clr-ui-menu:      $clr-grey-900;\n$clr-ui-button:    $clr-grey-900;\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Footnote reference\n  [id^=\"fnref:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 4px);\n    padding-top: px2rem(48px + 24px - 4px);\n  }\n\n  // Footnote\n  [id^=\"fn:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 3px);\n    padding-top: px2rem(48px + 24px - 3px);\n  }\n\n  // Footnote container\n  .footnote {\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n\n    // Footnote list - omit left indentation\n    > ol {\n      margin-left: 0;\n\n      // Footnote item - footnote items can contain lists, so we need to scope\n      // the spacing adjustments to the top-level footnote item.\n      > li {\n        transition: color 125ms;\n\n        // Darken color on target\n        &:target {\n          color: var(--md-default-fg-color);\n        }\n\n        // Show backreferences on footnote hover\n        &:hover  .footnote-backref,\n        &:target .footnote-backref {\n          transform: translateX(0);\n          opacity: 1;\n        }\n\n        // Adjust spacing on first child\n        > :first-child {\n          margin-top: 0;\n        }\n      }\n    }\n  }\n\n  // Footnote backreference\n  .footnote-backref {\n    display: inline-block;\n    color: var(--md-typeset-a-color);\n    // Hack: omit Unicode arrow for replacement with icon\n    font-size: 0;\n    vertical-align: text-bottom;\n    transform: translateX(px2rem(5px));\n    opacity: 0;\n    transition:\n      color     250ms,\n      transform 250ms 250ms,\n      opacity   125ms 250ms;\n\n    // [print]: Show footnote backreferences\n    @media print {\n      color: var(--md-typeset-a-color);\n      transform: translateX(0);\n      opacity: 1;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      transform: translateX(px2rem(-5px));\n    }\n\n    // Adjust color on hover\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Footnote backreference icon\n    &::before {\n      display: inline-block;\n      width: px2rem(16px);\n      height: px2rem(16px);\n      background-color: currentColor;\n      mask-image: var(--md-footnotes-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Headerlink\n  .headerlink {\n    display: inline-block;\n    margin-left: px2rem(10px);\n    color: var(--md-default-fg-color--lighter);\n    opacity: 0;\n    transition:\n      color      250ms,\n      opacity    125ms;\n\n    // [print]: Hide headerlinks\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(10px);\n      margin-left: initial;\n    }\n  }\n\n  // Show headerlinks on parent hover\n  :hover  > .headerlink,\n  :target > .headerlink,\n  .headerlink:focus {\n    opacity: 1;\n    transition:\n      color      250ms,\n      opacity    125ms;\n  }\n\n  // Adjust color on parent target or focus/hover\n  :target > .headerlink,\n  .headerlink:focus,\n  .headerlink:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Adjust scroll offset for all elements with `id` attributes - general scroll\n  // margin offset for anything that can be targeted. Browser support is pretty\n  // decent by now, but Edge <79 and Safari (iOS and macOS) still don't support\n  // it properly, so we settle with a cross-browser anchor correction solution.\n  :target {\n    scroll-margin-top: px2rem(48px + 24px);\n  }\n\n  // Adjust scroll offset for headlines of level 1-3\n  h1:target,\n  h2:target,\n  h3:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 4px);\n      padding-top: px2rem(48px + 24px - 4px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 4\n  h4:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 3px);\n      padding-top: px2rem(48px + 24px - 3px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 5-6\n  h5:target,\n  h6:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px);\n      padding-top: px2rem(48px + 24px);\n      content: \"\";\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Arithmatex container\n  div.arithmatex {\n    overflow: auto;\n\n    // [mobile -]: Align with body copy\n    @include break-to-device(mobile) {\n      margin: 0 px2rem(-16px);\n    }\n\n    // Arithmatex content\n    > * {\n      width: min-content;\n      // stylelint-disable-next-line declaration-no-important\n      margin: 1em auto !important;\n      padding: 0 px2rem(16px);\n      touch-action: auto;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Deletion, addition or comment\n  del.critic,\n  ins.critic,\n  .critic.comment {\n    box-decoration-break: clone;\n  }\n\n  // Deletion\n  del.critic {\n    background-color: var(--md-typeset-del-color);\n  }\n\n  // Addition\n  ins.critic {\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Comment\n  .critic.comment {\n    color: var(--md-code-hl-comment-color);\n\n    // Comment opening mark\n    &::before {\n      content: \"/* \";\n    }\n\n    // Comment closing mark\n    &::after {\n      content: \" */\";\n    }\n  }\n\n  // Critic block\n  .critic.block {\n    display: block;\n    margin: 1em 0;\n    padding-right: px2rem(16px);\n    padding-left: px2rem(16px);\n    overflow: auto;\n    box-shadow: none;\n\n    // Adjust spacing on first child\n    > :first-child {\n      margin-top: 0.5em;\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0.5em;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Details\n  details {\n    @extend .admonition;\n\n    display: flow-root;\n    padding-top: 0;\n    overflow: visible;\n\n    // Details title icon - rotate icon on transition to open state\n    &[open] > summary::after {\n      transform: rotate(90deg);\n    }\n\n    // Adjust spacing for details in closed state\n    &:not([open]) {\n      padding-bottom: 0;\n      box-shadow: none;\n\n      // Hack: we cannot set `overflow: hidden` on the `details` element (which\n      // is why we set it to `overflow: visible`, as the outline would not be\n      // visible when focusing. Therefore, we must set the border radius on the\n      // summary explicitly.\n      > summary {\n        border-radius: px2rem(2px);\n      }\n    }\n\n    // Hack: omit margin collapse\n    &::after {\n      display: table;\n      content: \"\";\n    }\n  }\n\n  // Details title\n  summary {\n    @extend .admonition-title;\n\n    display: block;\n    min-height: px2rem(20px);\n    padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);\n    border-top-left-radius: px2rem(2px);\n    border-top-right-radius: px2rem(2px);\n    cursor: pointer;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: px2rem(8px) px2rem(44px) px2rem(8px) px2rem(36px);\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Details marker\n    &::after {\n      position: absolute;\n      top: px2rem(8px);\n      right: px2rem(8px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: currentColor;\n      mask-image: var(--md-details-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      transform: rotate(0deg);\n      transition: transform 250ms;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(8px);\n        transform: rotate(180deg);\n      }\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Emoji and icon container\n  .emojione,\n  .twemoji,\n  .gemoji {\n    display: inline-flex;\n    height: px2em(18px);\n    vertical-align: text-top;\n\n    // Icon - inlined via mkdocs-material-extensions\n    svg {\n      width: px2em(18px);\n      max-height: 100%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: syntax highlighting\n// ----------------------------------------------------------------------------\n\n// Code block\n.highlight {\n  .o,   // Operator\n  .ow { // Operator, word\n    color: var(--md-code-hl-operator-color);\n  }\n\n  .p {  // Punctuation\n    color: var(--md-code-hl-punctuation-color);\n  }\n\n  .cpf, // Comment, preprocessor file\n  .l,   // Literal\n  .s,   // Literal, string\n  .sb,  // Literal, string backticks\n  .sc,  // Literal, string char\n  .s2,  // Literal, string double\n  .si,  // Literal, string interpol\n  .s1,  // Literal, string single\n  .ss { // Literal, string symbol\n    color: var(--md-code-hl-string-color);\n  }\n\n  .cp,  // Comment, pre-processor\n  .se,  // Literal, string escape\n  .sh,  // Literal, string heredoc\n  .sr,  // Literal, string regex\n  .sx { // Literal, string other\n    color: var(--md-code-hl-special-color);\n  }\n\n  .m,   // Number\n  .mb,  // Number, binary\n  .mf,  // Number, float\n  .mh,  // Number, hex\n  .mi,  // Number, integer\n  .il,  // Number, integer long\n  .mo { // Number, octal\n    color: var(--md-code-hl-number-color);\n  }\n\n  .k,   // Keyword,\n  .kd,  // Keyword, declaration\n  .kn,  // Keyword, namespace\n  .kp,  // Keyword, pseudo\n  .kr,  // Keyword, reserved\n  .kt { // Keyword, type\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .kc,  // Keyword, constant\n  .n {  // Name\n    color: var(--md-code-hl-name-color);\n  }\n\n  .no,  // Name, constant\n  .nb,  // Name, builtin\n  .bp { // Name, builtin pseudo\n    color: var(--md-code-hl-constant-color);\n  }\n\n  .nc,  // Name, class\n  .ne,  // Name, exception\n  .nf,  // Name, function\n  .nn { // Name, namespace\n    color: var(--md-code-hl-function-color);\n  }\n\n  .nd,  // Name, decorator\n  .ni,  // Name, entity\n  .nl,  // Name, label\n  .nt { // Name, tag\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .c,   // Comment\n  .cm,  // Comment, multiline\n  .c1,  // Comment, single\n  .ch,  // Comment, shebang\n  .cs,  // Comment, special\n  .sd { // Literal, string doc\n    color: var(--md-code-hl-comment-color);\n  }\n\n  .na,  // Name, attribute\n  .nv,  // Variable,\n  .vc,  // Variable, class\n  .vg,  // Variable, global\n  .vi { // Variable, instance\n    color: var(--md-code-hl-variable-color);\n  }\n\n  .ge,  // Generic, emph\n  .gr,  // Generic, error\n  .gh,  // Generic, heading\n  .go,  // Generic, output\n  .gp,  // Generic, prompt\n  .gs,  // Generic, strong\n  .gu,  // Generic, subheading\n  .gt { // Generic, traceback\n    color: var(--md-code-hl-generic-color);\n  }\n\n  .gd,  // Diff, delete\n  .gi { // Diff, insert\n    margin: 0 px2em(-2px);\n    padding: 0 px2em(2px);\n    border-radius: px2rem(2px);\n  }\n\n  .gd { // Diff, delete\n    background-color: var(--md-typeset-del-color);\n  }\n\n  .gi { // Diff, insert\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Highlighted line\n  .hll {\n    display: block;\n    margin: 0 px2em(-16px, 13.6px);\n    padding: 0 px2em(16px, 13.6px);\n    background-color: var(--md-code-hl-color);\n  }\n\n  // Code block line numbers (inline)\n  [data-linenos]::before {\n    position: sticky;\n    left: px2em(-16px, 13.6px);\n    float: left;\n    margin-right: px2em(16px, 13.6px);\n    margin-left: px2em(-16px, 13.6px);\n    padding-left: px2em(16px, 13.6px);\n    color: var(--md-default-fg-color--light);\n    background-color: var(--md-code-bg-color);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n    content: attr(data-linenos);\n    user-select: none;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Code block with line numbers\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n\n  // Set table elements to block layout, because otherwise the whole flexbox\n  // hacking won't work correctly\n  tbody,\n  td {\n    display: block;\n    padding: 0;\n  }\n\n  // We need to use flexbox layout, because otherwise it's not possible to\n  // make the code container scroll while keeping the line numbers static\n  tr {\n    display: flex;\n  }\n\n  // The pre tags are nested inside a table, so we need to omit the margin\n  // because it collapses below all the overflows\n  pre {\n    margin: 0;\n  }\n\n  // Code block line numbers - disable user selection, so code can be easily\n  // copied without accidentally also copying the line numbers\n  .linenos {\n    padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n    padding-right: 0;\n    font-size: px2em(13.6px);\n    background-color: var(--md-code-bg-color);\n    user-select: none;\n  }\n\n  // Code block line numbers container\n  .linenodiv {\n    padding-right: px2em(8px, 13.6px);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n\n    // Adjust colors and alignment\n    pre {\n      color: var(--md-default-fg-color--light);\n      text-align: right;\n    }\n  }\n\n  // Code block container - stretch to remaining space\n  .code {\n    flex: 1;\n    overflow: hidden;\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Code block with line numbers\n  .highlighttable {\n    margin: 1em 0;\n    direction: ltr;\n    border-radius: px2rem(2px);\n\n    // Omit rounded borders on contained code block\n    code {\n      border-radius: 0;\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Top-level code block\n    > .highlight {\n      margin: 1em px2rem(-16px);\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n\n      // Omit rounded borders\n      code {\n        border-radius: 0;\n      }\n    }\n\n    // Top-level code block with line numbers\n    > .highlighttable {\n      margin: 1em px2rem(-16px);\n      border-radius: 0;\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tabbed block content\n  .tabbed-content {\n    display: none;\n    order: 99;\n    width: 100%;\n    box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);\n\n    // [print]: Show all tabs (even hidden ones) when printing\n    @media print {\n      display: block;\n      order: initial;\n    }\n\n    // Code block is the only child of a tab - remove margin and mirror\n    // previous (now deprecated) SuperFences code block grouping behavior\n    > pre:only-child,\n    > .highlight:only-child pre,\n    > .highlighttable:only-child {\n      margin: 0;\n\n      // Omit rounded borders\n      > code {\n        border-top-left-radius: 0;\n        border-top-right-radius: 0;\n      }\n    }\n\n    // Adjust spacing for nested tab\n    > .tabbed-set {\n      margin: 0;\n    }\n  }\n\n  // Tabbed block container\n  .tabbed-set {\n    position: relative;\n    display: flex;\n    flex-wrap: wrap;\n    margin: 1em 0;\n    border-radius: px2rem(2px);\n\n    // Tab radio button - the Tabbed extension will generate radio buttons with\n    // labels, so tabs can be triggered without the necessity for JavaScript.\n    // This is pretty cool, as it has great accessibility out-of-the box, so\n    // we just hide the radio button and toggle the label color for indication.\n    > input {\n      position: absolute;\n      width: 0;\n      height: 0;\n      opacity: 0;\n\n      // Tab label for checked radio button\n      &:checked + label {\n        color: var(--md-accent-fg-color);\n        border-color: var(--md-accent-fg-color);\n\n        // Show tabbed block content\n        + .tabbed-content {\n          display: block;\n        }\n      }\n\n      // Tab label on focus\n      &:focus + label {\n        outline-style: auto;\n      }\n\n      // Hide outline for pointer devices\n      &:not(.focus-visible) + label {\n        outline: none;\n        -webkit-tap-highlight-color: transparent;\n      }\n    }\n\n    // Tab label\n    > label {\n      z-index: 1;\n      width: auto;\n      padding: px2em(12px, 12.8px) 1.25em px2em(10px, 12.8px);\n      color: var(--md-default-fg-color--light);\n      font-weight: 700;\n      font-size: px2rem(12.8px);\n      border-bottom: px2rem(2px) solid transparent;\n      cursor: pointer;\n      transition: color 250ms;\n\n      // Tab label on hover\n      &:hover {\n        color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tasklist item\n  .task-list-item {\n    position: relative;\n    list-style-type: none;\n\n    // Make checkbox items align with normal list items, but position\n    // everything in ems for correct layout at smaller font sizes\n    [type=\"checkbox\"] {\n      position: absolute;\n      top: 0.45em;\n      left: -2em;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: -2em;\n        left: initial;\n      }\n    }\n  }\n\n  // Hide native checkbox, when custom classes are enabled\n  .task-list-control [type=\"checkbox\"] {\n    z-index: -1;\n    opacity: 0;\n  }\n\n  // Tasklist indicator in unchecked state\n  .task-list-indicator::before {\n    position: absolute;\n    top: 0.15em;\n    left: px2em(-24px);\n    width: px2em(20px);\n    height: px2em(20px);\n    background-color: var(--md-default-fg-color--lightest);\n    mask-image: var(--md-tasklist-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: px2em(-24px);\n      left: initial;\n    }\n  }\n\n  // Tasklist indicator in checked state\n  [type=\"checkbox\"]:checked + .task-list-indicator::before {\n    background-color: $clr-green-a400;\n    mask-image: var(--md-tasklist-icon--checked);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // [tablet +]: Allow for rendering content as sidebars\n  @include break-from-device(tablet) {\n\n    // Modifier to float block elements\n    .inline {\n      float: left;\n      width: px2rem(234px);\n      margin-top: 0;\n      margin-right: px2rem(16px);\n      margin-bottom: px2rem(16px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n      }\n\n      // Modifier to move to end (ltr: right, rtl: left)\n      &.end {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          float: left;\n          margin-right: px2rem(16px);\n          margin-left: 0;\n        }\n      }\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/main.77f3fd56.min.css b/5.4/assets/stylesheets/main.77f3fd56.min.css
deleted file mode 100644 (file)
index 8834c5c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-@charset "UTF-8";html{box-sizing:border-box;-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}*,:after,:before{box-sizing:inherit}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{display:block;box-sizing:initial;height:.05rem;padding:0;overflow:visible;border:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;font-size:inherit;background:transparent;border:0}input{border:0;outline:none}:root{--md-default-fg-color:rgba(0,0,0,0.87);--md-default-fg-color--light:rgba(0,0,0,0.54);--md-default-fg-color--lighter:rgba(0,0,0,0.32);--md-default-fg-color--lightest:rgba(0,0,0,0.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,0.7);--md-default-bg-color--lighter:hsla(0,0%,100%,0.3);--md-default-bg-color--lightest:hsla(0,0%,100%,0.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}:root>*{--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,0.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,0.5);--md-typeset-del-color:rgba(245,80,61,0.15);--md-typeset-ins-color:rgba(11,213,112,0.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,0.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,0.3);--md-footer-bg-color:rgba(0,0,0,0.87);--md-footer-bg-color--dark:rgba(0,0,0,0.32)}.md-icon svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family,_),SFMono-Regular,Consolas,Menlo,monospace}:root{--md-typeset-table--ascending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 4h2v12l5.5-5.5 1.42 1.42L12 19.84l-7.92-7.92L5.5 10.5 11 16V4z'/></svg>");--md-typeset-table--descending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z'/></svg>")}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact;color-adjust:exact}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{display:flow-root;margin:1em 0}.md-typeset h1{margin:0 0 1.25em;color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:1.6em 0 .64em;font-size:1.5625em;line-height:1.4}.md-typeset h3{margin:1.6em 0 .8em;font-weight:400;font-size:1.25em;line-height:1.5;letter-spacing:-.01em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{margin:1em 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{margin:1.25em 0;color:var(--md-default-fg-color--light);font-weight:700;font-size:.8em;letter-spacing:-.01em}.md-typeset h5{text-transform:uppercase}.md-typeset hr{display:flow-root;margin:1.5em 0;border-bottom:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{padding:0 .2941176471em;font-size:.85em;word-break:break-word;background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset code:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:initial;padding:initial;background-color:initial;box-shadow:none}.md-typeset a>code{color:currentColor}.md-typeset pre{position:relative;line-height:1.4}.md-typeset pre>code{display:block;margin:0;padding:.7720588235em 1.1764705882em;overflow:auto;word-break:normal;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;touch-action:auto;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:44.9375em){.md-typeset>pre{margin:1em -.8rem}.md-typeset>pre code{border-radius:0}}.md-typeset kbd{display:inline-block;padding:0 .6666666667em;color:var(--md-default-fg-color);font-size:.75em;vertical-align:text-top;word-break:break-word;background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset}.md-typeset mark{color:inherit;word-break:break-word;background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{text-decoration:none;border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:focus:after,.md-typeset abbr[title]:hover:after{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:absolute;left:0;display:inline-block;width:auto;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;max-width:80%;margin-top:2em;padding:.2rem .3rem;color:var(--md-default-bg-color);font-size:.7rem;background-color:var(--md-default-fg-color);border-radius:.1rem;content:attr(title)}}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;color:var(--md-default-fg-color--light);border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid var(--md-default-fg-color--lighter);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1.5em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset img,.md-typeset svg{max-width:100%;height:auto}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset figure{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;margin:0 auto;text-align:center}.md-typeset figure img{display:block}.md-typeset figcaption{max-width:24rem;margin:1em auto 2em;font-style:italic}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){display:inline-block;max-width:100%;overflow:auto;font-size:.64rem;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .05rem rgba(0,0,0,.1);touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.9375em 1.25em;color:var(--md-default-bg-color);vertical-align:top;background-color:var(--md-default-fg-color--light)}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{padding:.9375em 1.25em;vertical-align:top;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset table:not([class]) tr{transition:background-color 125ms}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}.md-typeset table th[role=columnheader]:after{display:inline-block;width:1.2em;height:1.2em;margin-left:.5em;vertical-align:sub;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--ascending);mask-image:var(--md-typeset-table--ascending)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--descending);mask-image:var(--md-typeset-table--descending)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{height:100%;overflow-x:hidden;font-size:125%}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;font-size:.5rem;background-color:var(--md-default-bg-color)}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-state=lock]{position:fixed}}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.md-toggle{display:none}.md-skip{position:fixed;z-index:-1;margin:.5rem;padding:.3rem .5rem;color:var(--md-default-bg-color);font-size:.64rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(.4rem);opacity:0}.md-skip:focus{z-index:10;transform:translateY(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms}@page{margin:25mm}.md-announce{overflow:auto;background-color:var(--md-footer-bg-color)}@media print{.md-announce{display:none}}.md-announce__inner{margin:.6rem auto;padding:0 .8rem;color:var(--md-footer-fg-color);font-size:.7rem}:root{--md-clipboard-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1z'/></svg>")}.md-clipboard{position:absolute;top:.5em;right:.5em;z-index:1;width:1.5em;height:1.5em;color:var(--md-default-fg-color--lightest);border-radius:.1rem;cursor:pointer;transition:color .25s}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{display:block;width:1.125em;height:1.125em;margin:0 auto;background-color:currentColor;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-content{flex-grow:1;overflow:hidden;scroll-padding-top:51.2rem}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){.md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem;margin-left:.8rem}.md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:.8rem;margin-left:1.2rem}}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__button{float:right;margin:.4rem 0 .4rem .4rem;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left;margin-right:.4rem;margin-left:0}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}.md-dialog{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:fixed;right:.8rem;bottom:.8rem;left:auto;z-index:2;min-width:11.1rem;padding:.4rem .6rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(100%);opacity:0;transition:transform 0ms .4s,opacity .4s;pointer-events:none}@media print{.md-dialog{display:none}}[dir=rtl] .md-dialog{right:auto;left:.8rem}.md-dialog[data-md-state=open]{transform:translateY(0);opacity:1;transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s;pointer-events:auto}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-typeset .md-button{display:inline-block;padding:.625em 2em;color:var(--md-primary-fg-color);font-weight:700;border:.1rem solid;border-radius:.1rem;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{color:var(--md-accent-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .md-input{height:1.8rem;padding:0 .6rem;font-size:.8rem;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transition:box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{box-shadow:0 .4rem 1rem rgba(0,0,0,.15),0 .025rem .05rem rgba(0,0,0,.15)}.md-typeset .md-input--stretch{width:100%}.md-header{position:-webkit-sticky;position:sticky;top:0;right:0;left:0;z-index:2;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent;transition:color .25s,background-color .25s}@media print{.md-header{display:none}}.md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),color .25s,background-color .25s,box-shadow .25s}.md-header[data-md-state=hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),color .25s,background-color .25s,box-shadow .25s}.md-header__inner{display:flex;align-items:center;padding:0 .2rem}.md-header__button{position:relative;z-index:1;display:inline-block;margin:.2rem;padding:.4rem;color:currentColor;vertical-align:middle;cursor:pointer;transition:opacity .25s}.md-header__button:focus,.md-header__button:hover{opacity:.7}.md-header__button:not(.focus-visible){outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{position:absolute;display:flex;max-width:100%;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-header__topic+.md-header__topic{z-index:-1;transform:translateX(1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__title{flex-grow:1;height:2.4rem;margin-right:.4rem;margin-left:1rem;font-size:.9rem;line-height:2.4rem}.md-header__title[data-md-state=active] .md-header__topic{z-index:-1;transform:translateX(-1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic{transform:translateX(1.25rem)}.md-header__title[data-md-state=active] .md-header__topic+.md-header__topic{z-index:0;transform:translateX(0);opacity:1;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:auto}.md-header__title>.md-header__ellipsis{position:relative;width:100%;height:100%}.md-header__options{display:flex;flex-shrink:0;max-width:100%;white-space:nowrap;transition:max-width 0ms .25s,opacity .25s .25s}.md-header__options>[data-md-state=hidden]{display:none}[data-md-toggle=search]:checked~.md-header .md-header__options{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){.md-header__source{display:block;width:11.7rem;max-width:11.7rem;margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem;margin-left:0}}@media screen and (min-width:76.25em){.md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-footer{color:var(--md-footer-fg-color);background-color:var(--md-footer-bg-color)}@media print{.md-footer{display:none}}.md-footer__inner{padding:.2rem;overflow:auto}.md-footer__link{display:flex;padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}@media screen and (min-width:45em){.md-footer__link{width:50%}}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}.md-footer__link--prev{float:left}@media screen and (max-width:44.9375em){.md-footer__link--prev{width:25%}.md-footer__link--prev .md-footer__title{display:none}}[dir=rtl] .md-footer__link--prev{float:right}[dir=rtl] .md-footer__link--prev svg{transform:scaleX(-1)}.md-footer__link--next{float:right;text-align:right}@media screen and (max-width:44.9375em){.md-footer__link--next{width:75%}}[dir=rtl] .md-footer__link--next{float:left;text-align:left}[dir=rtl] .md-footer__link--next svg{transform:scaleX(-1)}.md-footer__title{position:relative;flex-grow:1;max-width:calc(100% - 2.4rem);padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-footer-copyright{width:100%;margin:auto .6rem;padding:.4rem 0;color:var(--md-footer-fg-color--lighter);font-size:.64rem}@media screen and (min-width:45em){.md-footer-copyright{width:auto}}.md-footer-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-footer-social{padding:.6rem 0}}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-footer-social__link svg{max-height:.8rem;vertical-align:-25%;fill:currentColor}:root{--md-nav-icon--prev:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z'/></svg>");--md-nav-icon--next:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>");--md-toc-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9h14V7H3v2m0 4h14v-2H3v2m0 4h14v-2H3v2m16 0h2v-2h-2v2m0-10v2h2V7h-2m0 6h2v-2h-2v2z'/></svg>")}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{width:auto;height:100%}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{display:block;width:2.4rem;height:2.4rem;fill:currentColor}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__link{display:block;margin-top:.625em;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:color 125ms;scroll-snap-align:start}.md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{display:block;width:100%;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);background-color:currentColor}.md-nav--primary .md-nav__link[for=__toc]~.md-nav,.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{position:absolute;top:0;right:0;left:0;z-index:1;display:flex;flex-direction:column;height:100%;background-color:var(--md-default-bg-color)}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;color:var(--md-default-fg-color--light);font-weight:400;line-height:2.4rem;white-space:nowrap;background-color:var(--md-default-fg-color--lightest);cursor:pointer}.md-nav--primary .md-nav__title .md-nav__icon{position:absolute;top:.4rem;left:.4rem;display:block;width:1.2rem;height:1.2rem;margin:.2rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem;left:auto}.md-nav--primary .md-nav__title .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav--primary .md-nav__title~.md-nav__list{overflow-y:auto;background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;-webkit-scroll-snap-type:y mandatory;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-nav--primary .md-nav__title .md-logo{position:absolute;top:.2rem;left:.2rem;display:block;margin:.2rem;padding:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-logo{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link .md-nav__icon{position:absolute;top:50%;right:.6rem;width:1.2rem;height:1.2rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{right:auto;left:.6rem}.md-nav--primary .md-nav__link .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:initial}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;transform:translateX(100%);opacity:0;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{transform:translateX(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{display:block;padding:0 .2rem;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color--dark)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:block;padding-right:2.4rem;scroll-snap-align:none}[dir=rtl] .md-nav--integrated .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav,.md-nav__toggle:indeterminate~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__title{display:block;padding:0;pointer-events:none;scroll-snap-align:start}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{float:right;width:.9rem;height:.9rem;transition:transform .25s}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:after{display:inline-block;width:100%;height:100%;vertical-align:-.1rem;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav>.md-nav__title{display:block;padding:0 .6rem;pointer-events:none;scroll-snap-align:start}.md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav__item{padding-right:.6rem}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:block;margin-bottom:1.25em;border-left:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav>.md-nav__title{display:none}}:root{--md-search-result-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7c-.41-.25-.8-.56-1.14-.9-.33-.33-.61-.7-.86-1.1H6V4h7v5h5v1.18c.71.16 1.39.43 2 .82V8l-6-6m6.31 16.9c1.33-2.11.69-4.9-1.4-6.22-2.11-1.33-4.91-.68-6.22 1.4-1.34 2.11-.69 4.89 1.4 6.22 1.46.93 3.32.93 4.79.02L22 23.39 23.39 22l-3.08-3.1m-3.81.1a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5z'/></svg>")}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{z-index:1;opacity:0}@media screen and (max-width:59.9375em){.md-search__overlay{position:absolute;top:.2rem;left:-2.2rem;width:2rem;height:2rem;overflow:hidden;background-color:var(--md-default-bg-color);border-radius:1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;pointer-events:none}[dir=rtl] .md-search__overlay{right:-2.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:rgba(0,0,0,.54);cursor:pointer;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){.md-search__inner{position:fixed;top:0;left:100%;z-index:2;width:100%;height:100%;transform:translateX(5%);opacity:0;transition:right 0ms .3s,left 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;transform:translateX(0);opacity:1;transition:right 0ms 0ms,left 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;transform:translateX(-5%)}}@media screen and (min-width:60em){.md-search__inner{position:relative;float:right;width:11.7rem;padding:.1rem 0;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{position:relative}@media screen and (min-width:60em){.md-search__form{border-radius:.1rem}}.md-search__input{position:relative;z-index:2;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;transition:color .25s,background-color .25s,box-shadow .25s}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s;transition:color .25s}.md-search__input::-moz-placeholder{-moz-transition:color .25s;transition:color .25s}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}[data-md-toggle=search]:checked~.md-header .md-search__input{box-shadow:0 0 .6rem rgba(0,0,0,.07)}@media screen and (max-width:59.9375em){.md-search__input{width:100%;height:2.4rem;font-size:.9rem}}@media screen and (min-width:60em){.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;color:inherit;font-size:.8rem;background-color:rgba(0,0,0,.26);border-radius:.1rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}.md-search__input::-webkit-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{color:var(--md-default-fg-color);text-overflow:clip;background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{position:absolute;z-index:2;width:1.2rem;height:1.2rem;cursor:pointer;transition:color .25s,opacity .25s}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){.md-search__icon[for=__search]{top:.6rem;left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem;left:auto}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}.md-search__icon[type=reset]{top:.3rem;right:.5rem;transform:scale(.75);opacity:0;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:none}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}@media screen and (max-width:59.9375em){.md-search__icon[type=reset]{top:.6rem;right:.8rem}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{transform:scale(1);opacity:1;pointer-events:auto}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;z-index:1;width:100%;overflow:hidden;border-radius:0 0 .1rem .1rem}@media screen and (max-width:59.9375em){.md-search__output{top:2.4rem;bottom:0}}@media screen and (min-width:60em){.md-search__output{top:1.9rem;opacity:0;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}}.md-search__scrollwrap{height:100%;overflow-y:auto;background-color:var(--md-default-bg-color);-webkit-backface-visibility:hidden;backface-visibility:hidden;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1), (max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{padding:0 .8rem;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;background-color:var(--md-default-fg-color--lightest);scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}}.md-search-result__list{margin:0;padding:0;list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;transition:background-color .25s;scroll-snap-align:start}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{display:block;padding:.75em .8rem;color:var(--md-typeset-a-color);font-size:.64rem;outline:0;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__more summary:focus,.md-search-result__more summary:hover{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-search-result__more summary::-webkit-details-marker,.md-search-result__more summary::marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{position:relative;padding:0 .8rem;overflow:hidden}@media screen and (min-width:60em){.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-weight:400;font-size:.8rem;line-height:1.4}.md-search-result__icon{position:absolute;left:0;width:1.2rem;height:1.2rem;margin:.5rem;color:var(--md-default-fg-color--light)}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{display:inline-block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-search-result__icon{right:0;left:auto}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{margin:.5em 0;font-weight:700;font-size:.64rem;line-height:1.6}.md-search-result__teaser{display:-webkit-box;max-height:2rem;margin:.5em 0;overflow:hidden;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}@media screen and (max-width:44.9375em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}.md-search-result__teaser mark{text-decoration:underline;background-color:initial}.md-search-result__terms{margin:.5em 0;font-size:.64rem;font-style:italic}.md-search-result mark{color:var(--md-accent-fg-color);background-color:initial}.md-select{position:relative;z-index:1}.md-select__inner{position:absolute;top:calc(100% - .2rem);left:50%;max-height:0;margin-top:.2rem;color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);transform:translate3d(-50%,.3rem,0);opacity:0;transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;transform:translate3d(-50%,0,0);opacity:1;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s}.md-select__inner:after{position:absolute;top:0;left:50%;width:0;height:0;margin-top:-.2rem;margin-left:-.2rem;border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);content:""}.md-select__list{max-height:inherit;margin:0;padding:0;overflow:auto;font-size:.8rem;list-style-type:none;border-radius:.1rem}.md-select__item{line-height:1.8rem}.md-select__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;cursor:pointer;transition:background-color .25s,color .25s;scroll-snap-align:start}[dir=rtl] .md-select__link{padding-right:.6rem;padding-left:1.2rem}.md-select__link:focus,.md-select__link:hover{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{position:-webkit-sticky;position:sticky;top:2.4rem;flex-shrink:0;align-self:flex-start;width:12.1rem;padding:1.2rem 0}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;z-index:3;display:block;width:12.1rem;height:100%;background-color:var(--md-default-bg-color);transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;-webkit-scroll-snap-type:none;-ms-scroll-snap-type:none;scroll-snap-type:none;overflow:hidden}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{position:fixed;top:0;z-index:3;width:0;height:0;background-color:rgba(0,0,0,.54);opacity:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}.md-source{display:block;font-size:.65rem;line-height:1.2;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity .25s}.md-source:focus,.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;width:2.4rem;height:2.4rem;vertical-align:middle}.md-source__icon svg{margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);margin-left:.6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{margin:0;padding:0;overflow:hidden;font-weight:700;font-size:.55rem;list-style-type:none;opacity:.75}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"·"}.md-source__fact:first-child:before{display:none}.md-tabs{width:100%;overflow:auto;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);transition:background-color .25s}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs__list{margin:0 0 0 .2rem;padding:0;white-space:nowrap;list-style:none;contain:content}[dir=rtl] .md-tabs__list{margin-right:.2rem;margin-left:0}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;font-size:.7rem;-webkit-backface-visibility:hidden;backface-visibility:hidden;opacity:.7;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden] .md-tabs__link{transform:translateY(50%);opacity:0;transition:transform 0ms .1s,opacity .1s}:root{--md-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'><path d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/></svg>")}.md-version{flex-shrink:0;height:2.4rem;font-size:.8rem}.md-version__current{position:relative;top:.05rem;margin-right:.4rem;margin-left:1.4rem}[dir=rtl] .md-version__current{margin-right:1.4rem;margin-left:.4rem}.md-version__current:after{display:inline-block;width:.4rem;height:.6rem;margin-left:.4rem;background-color:currentColor;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;content:""}[dir=rtl] .md-version__current:after{margin-right:.4rem;margin-left:0}.md-version__list{position:absolute;top:.15rem;z-index:1;max-height:1.8rem;margin:.2rem .8rem;padding:0;overflow:auto;color:var(--md-default-fg-color);list-style-type:none;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);opacity:0;transition:max-height 0ms .5s,opacity .25s .25s;-webkit-scroll-snap-type:y mandatory;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory}.md-version__list:focus-within,.md-version__list:hover{max-height:10rem;opacity:1;transition:max-height .25s,opacity .25s}.md-version__item{line-height:1.8rem}.md-version__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;white-space:nowrap;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}[dir=rtl] .md-version__link{padding-right:.6rem;padding-left:1.2rem}.md-version__link:focus,.md-version__link:hover{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");--md-admonition-icon--abstract:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z'/></svg>");--md-admonition-icon--info:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");--md-admonition-icon--tip:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");--md-admonition-icon--success:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/></svg>");--md-admonition-icon--question:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M15.07 11.25l-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");--md-admonition-icon--warning:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");--md-admonition-icon--failure:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z'/></svg>");--md-admonition-icon--danger:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z'/></svg>");--md-admonition-icon--bug:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");--md-admonition-icon--example:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");--md-admonition-icon--quote:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>")}.md-typeset .admonition,.md-typeset details{margin:1.5625em 0;padding:0 .6rem;overflow:hidden;color:var(--md-admonition-fg-color);font-size:.64rem;page-break-inside:avoid;background-color:var(--md-admonition-bg-color);border-left:.2rem solid #448aff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 .025rem .05rem rgba(0,0,0,.05)}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition-title,.md-typeset summary{position:relative;margin:0 -.6rem 0 -.8rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(68,138,255,.1);border-left:.2rem solid #448aff}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{margin:0 -.8rem 0 -.6rem;padding:.4rem 2rem .4rem .6rem;border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}.md-typeset .admonition-title:before,.md-typeset summary:before{position:absolute;left:.6rem;width:1rem;height:1rem;background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem;left:auto}.md-typeset .admonition-title code,.md-typeset summary code{margin:initial;padding:initial;color:currentColor;background-color:initial;border-radius:initial;box-shadow:none}.md-typeset .admonition-title+.tabbed-set:last-child,.md-typeset summary+.tabbed-set:last-child{margin-top:0}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:rgba(68,138,255,.1);border-color:#448aff}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-color:#00b0ff}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary,.md-typeset .summary>.admonition-title,.md-typeset .summary>summary,.md-typeset .tldr>.admonition-title,.md-typeset .tldr>summary{background-color:rgba(0,176,255,.1);border-color:#00b0ff}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before,.md-typeset .summary>.admonition-title:before,.md-typeset .summary>summary:before,.md-typeset .tldr>.admonition-title:before,.md-typeset .tldr>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-color:#00b8d4}.md-typeset .info>.admonition-title,.md-typeset .info>summary,.md-typeset .todo>.admonition-title,.md-typeset .todo>summary{background-color:rgba(0,184,212,.1);border-color:#00b8d4}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before,.md-typeset .todo>.admonition-title:before,.md-typeset .todo>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .hint>.admonition-title,.md-typeset .hint>summary,.md-typeset .important>.admonition-title,.md-typeset .important>summary,.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:rgba(0,191,165,.1);border-color:#00bfa5}.md-typeset .hint>.admonition-title:before,.md-typeset .hint>summary:before,.md-typeset .important>.admonition-title:before,.md-typeset .important>summary:before,.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-color:#00c853}.md-typeset .check>.admonition-title,.md-typeset .check>summary,.md-typeset .done>.admonition-title,.md-typeset .done>summary,.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:rgba(0,200,83,.1);border-color:#00c853}.md-typeset .check>.admonition-title:before,.md-typeset .check>summary:before,.md-typeset .done>.admonition-title:before,.md-typeset .done>summary:before,.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-color:#64dd17}.md-typeset .faq>.admonition-title,.md-typeset .faq>summary,.md-typeset .help>.admonition-title,.md-typeset .help>summary,.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:rgba(100,221,23,.1);border-color:#64dd17}.md-typeset .faq>.admonition-title:before,.md-typeset .faq>summary:before,.md-typeset .help>.admonition-title:before,.md-typeset .help>summary:before,.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-color:#ff9100}.md-typeset .attention>.admonition-title,.md-typeset .attention>summary,.md-typeset .caution>.admonition-title,.md-typeset .caution>summary,.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:rgba(255,145,0,.1);border-color:#ff9100}.md-typeset .attention>.admonition-title:before,.md-typeset .attention>summary:before,.md-typeset .caution>.admonition-title:before,.md-typeset .caution>summary:before,.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-color:#ff5252}.md-typeset .fail>.admonition-title,.md-typeset .fail>summary,.md-typeset .failure>.admonition-title,.md-typeset .failure>summary,.md-typeset .missing>.admonition-title,.md-typeset .missing>summary{background-color:rgba(255,82,82,.1);border-color:#ff5252}.md-typeset .fail>.admonition-title:before,.md-typeset .fail>summary:before,.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before,.md-typeset .missing>.admonition-title:before,.md-typeset .missing>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-color:#ff1744}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary,.md-typeset .error>.admonition-title,.md-typeset .error>summary{background-color:rgba(255,23,68,.1);border-color:#ff1744}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before,.md-typeset .error>.admonition-title:before,.md-typeset .error>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:rgba(245,0,87,.1);border-color:#f50057}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:rgba(124,77,255,.1);border-color:#7c4dff}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .cite>.admonition-title,.md-typeset .cite>summary,.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:hsla(0,0%,62%,.1);border-color:#9e9e9e}.md-typeset .cite>.admonition-title:before,.md-typeset .cite>summary:before,.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z'/></svg>")}.md-typeset [id^="fnref:"]:target{scroll-margin-top:0;margin-top:-3.4rem;padding-top:3.4rem}.md-typeset [id^="fn:"]:target{scroll-margin-top:0;margin-top:-3.45rem;padding-top:3.45rem}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{transition:color 125ms}.md-typeset .footnote li:target{color:var(--md-default-fg-color)}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{transform:translateX(0);opacity:1}.md-typeset .footnote li>:first-child{margin-top:0}.md-typeset .footnote-backref{display:inline-block;color:var(--md-typeset-a-color);font-size:0;vertical-align:text-bottom;transform:translateX(.25rem);opacity:0;transition:color .25s,transform .25s .25s,opacity 125ms .25s}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);transform:translateX(0);opacity:1}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{display:inline-block;width:.8rem;height:.8rem;background-color:currentColor;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;color:var(--md-default-fg-color--lighter);opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{scroll-margin-top:3.6rem}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{scroll-margin-top:0}.md-typeset h1:target:before,.md-typeset h2:target:before,.md-typeset h3:target:before{display:block;margin-top:-3.4rem;padding-top:3.4rem;content:""}.md-typeset h4:target{scroll-margin-top:0}.md-typeset h4:target:before{display:block;margin-top:-3.45rem;padding-top:3.45rem;content:""}.md-typeset h5:target,.md-typeset h6:target{scroll-margin-top:0}.md-typeset h5:target:before,.md-typeset h6:target:before{display:block;margin-top:-3.6rem;padding-top:3.6rem;content:""}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{width:-webkit-min-content;width:-moz-min-content;width:min-content;margin:1em auto!important;padding:0 .8rem;touch-action:auto}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;overflow:auto;box-shadow:none}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>")}.md-typeset details{display:flow-root;padding-top:0;overflow:visible}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){padding-bottom:0;box-shadow:none}.md-typeset details:not([open])>summary{border-radius:.1rem}.md-typeset details:after{display:table;content:""}.md-typeset summary{display:block;min-height:1rem;padding:.4rem 1.8rem .4rem 2rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;cursor:pointer}[dir=rtl] .md-typeset summary{padding:.4rem 2.2rem .4rem 1.8rem}.md-typeset summary:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset summary:after{position:absolute;top:.4rem;right:.4rem;width:1rem;height:1rem;background-color:currentColor;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transform:rotate(0deg);transition:transform .25s;content:""}[dir=rtl] .md-typeset summary:after{right:auto;left:.4rem;transform:rotate(180deg)}.md-typeset summary::-webkit-details-marker,.md-typeset summary::marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{width:1.125em;max-height:100%;fill:currentColor}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{margin:0 -.125em;padding:0 .125em;border-radius:.1rem}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em;background-color:var(--md-code-hl-color)}.highlight [data-linenos]:before{position:-webkit-sticky;position:sticky;left:-1.1764705882em;float:left;margin-right:1.1764705882em;margin-left:-1.1764705882em;padding-left:1.1764705882em;color:var(--md-default-fg-color--light);background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable{display:flow-root;overflow:hidden}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable .linenos{padding:.7720588235em 0 .7720588235em 1.1764705882em;font-size:.85em;background-color:var(--md-code-bg-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{padding-right:.5882352941em;box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;overflow:hidden}.md-typeset .highlighttable{margin:1em 0;direction:ltr;border-radius:.1rem}.md-typeset .highlighttable code{border-radius:0}@media screen and (max-width:44.9375em){.md-typeset>.highlight{margin:1em -.8rem}.md-typeset>.highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.highlight code{border-radius:0}.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.highlighttable .hll{margin:0 -.8rem;padding:0 .8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{position:relative;margin:0;color:inherit;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.md-typeset .keys span{padding:0 .2em;color:var(--md-default-fg-color--light)}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{padding-right:.4em;content:"⎇"}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{padding-right:.4em;content:"⌘"}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{padding-right:.4em;content:"⌃"}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{padding-right:.4em;content:"◆"}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{padding-right:.4em;content:"⌥"}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{padding-right:.4em;content:"⇧"}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{padding-right:.4em;content:"❖"}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{padding-right:.4em;content:"⊞"}.md-typeset .keys .key-arrow-down:before{padding-right:.4em;content:"↓"}.md-typeset .keys .key-arrow-left:before{padding-right:.4em;content:"←"}.md-typeset .keys .key-arrow-right:before{padding-right:.4em;content:"→"}.md-typeset .keys .key-arrow-up:before{padding-right:.4em;content:"↑"}.md-typeset .keys .key-backspace:before{padding-right:.4em;content:"⌫"}.md-typeset .keys .key-backtab:before{padding-right:.4em;content:"⇤"}.md-typeset .keys .key-caps-lock:before{padding-right:.4em;content:"⇪"}.md-typeset .keys .key-clear:before{padding-right:.4em;content:"⌧"}.md-typeset .keys .key-context-menu:before{padding-right:.4em;content:"☰"}.md-typeset .keys .key-delete:before{padding-right:.4em;content:"⌦"}.md-typeset .keys .key-eject:before{padding-right:.4em;content:"⏏"}.md-typeset .keys .key-end:before{padding-right:.4em;content:"⤓"}.md-typeset .keys .key-escape:before{padding-right:.4em;content:"⎋"}.md-typeset .keys .key-home:before{padding-right:.4em;content:"⤒"}.md-typeset .keys .key-insert:before{padding-right:.4em;content:"⎀"}.md-typeset .keys .key-page-down:before{padding-right:.4em;content:"⇟"}.md-typeset .keys .key-page-up:before{padding-right:.4em;content:"⇞"}.md-typeset .keys .key-print-screen:before{padding-right:.4em;content:"⎙"}.md-typeset .keys .key-tab:after{padding-left:.4em;content:"⇥"}.md-typeset .keys .key-num-enter:after{padding-left:.4em;content:"⌤"}.md-typeset .keys .key-enter:after{padding-left:.4em;content:"⏎"}.md-typeset .tabbed-content{display:none;order:99;width:100%;box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}@media print{.md-typeset .tabbed-content{display:block;order:0}}.md-typeset .tabbed-content>.highlight:only-child pre,.md-typeset .tabbed-content>.highlighttable:only-child,.md-typeset .tabbed-content>pre:only-child{margin:0}.md-typeset .tabbed-content>.highlight:only-child pre>code,.md-typeset .tabbed-content>.highlighttable:only-child>code,.md-typeset .tabbed-content>pre:only-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-content>.tabbed-set{margin:0}.md-typeset .tabbed-set{position:relative;display:flex;flex-wrap:wrap;margin:1em 0;border-radius:.1rem}.md-typeset .tabbed-set>input{position:absolute;width:0;height:0;opacity:0}.md-typeset .tabbed-set>input:checked+label{color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:checked+label+.tabbed-content{display:block}.md-typeset .tabbed-set>input:focus+label{outline-style:auto}.md-typeset .tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.md-typeset .tabbed-set>label{z-index:1;width:auto;padding:.9375em 1.25em .78125em;color:var(--md-default-fg-color--light);font-weight:700;font-size:.64rem;border-bottom:.1rem solid transparent;cursor:pointer;transition:color .25s}.md-typeset .tabbed-set>label:hover{color:var(--md-accent-fg-color)}:root{--md-tasklist-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>");--md-tasklist-icon--checked:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>")}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control [type=checkbox]{z-index:-1;opacity:0}.md-typeset .task-list-indicator:before{position:absolute;top:.15em;left:-1.5em;width:1.25em;height:1.25em;background-color:var(--md-default-fg-color--lightest);-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em;left:auto}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media screen and (min-width:45em){.md-typeset .inline{float:left;width:11.7rem;margin-top:0;margin-right:.8rem;margin-bottom:.8rem}.md-typeset .inline.end,[dir=rtl] .md-typeset .inline{float:right;margin-right:0;margin-left:.8rem}[dir=rtl] .md-typeset .inline.end{float:left;margin-right:.8rem;margin-left:0}}
-/*# sourceMappingURL=main.77f3fd56.min.css.map */
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/main.77f3fd56.min.css.map b/5.4/assets/stylesheets/main.77f3fd56.min.css.map
deleted file mode 100644 (file)
index 20accb2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_reset.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","node_modules/material-shadows/material-shadows.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_announce.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/main.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Keyframes\n// ----------------------------------------------------------------------------\n\n// Show repository facts\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n\n  100% {\n    height: px2rem(13px);\n  }\n}\n\n// Show repository fact\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n\n  50% {\n    opacity: 0;\n  }\n\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Repository information\n.md-source {\n  display: block;\n  font-size: px2rem(13px);\n  line-height: 1.2;\n  white-space: nowrap;\n  // Hack: promote to own layer to reduce jitter\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n\n  // Repository information on focus/hover\n  &:focus,\n  &:hover {\n    opacity: 0.7;\n  }\n\n  // Repository icon\n  &__icon {\n    display: inline-block;\n    width: px2rem(48px);\n    height: px2rem(48px);\n    vertical-align: middle;\n\n    // Align with margin only (as opposed to normal button alignment)\n    svg {\n      margin-top: px2rem(12px);\n      margin-left: px2rem(12px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(12px);\n        margin-left: initial;\n      }\n    }\n\n    // Adjust spacing if icon is present\n    + .md-source__repository {\n      margin-left: px2rem(-40px);\n      padding-left: px2rem(40px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(-40px);\n        margin-left: initial;\n        padding-right: px2rem(40px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Repository name\n  &__repository {\n    display: inline-block;\n    max-width: calc(100% - #{px2rem(24px)});\n    margin-left: px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n    vertical-align: middle;\n  }\n\n  // Repository facts\n  &__facts {\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n    font-weight: 700;\n    font-size: px2rem(11px);\n    list-style-type: none;\n    opacity: 0.75;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__facts--done 250ms ease-in;\n    }\n  }\n\n  // Repository fact\n  &__fact {\n    float: left;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: right;\n    }\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__fact--done 400ms ease-out;\n    }\n\n    // Middle dot before fact\n    &::before {\n      margin: 0 px2rem(2px);\n      content: \"\\00B7\";\n    }\n\n    // Remove middle dot on first fact\n    &:first-child::before {\n      display: none;\n    }\n  }\n}\n","@charset \"UTF-8\";\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n}\n\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\na {\n  color: inherit;\n  text-decoration: none;\n}\n\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: 0.05rem;\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  line-height: 1em;\n}\n\nimg {\n  border-style: none;\n}\n\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\ninput {\n  border: 0;\n  outline: none;\n}\n\n:root {\n  --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color: hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n:root > * {\n  --md-code-fg-color: hsla(200, 18%, 26%, 1);\n  --md-code-bg-color: hsla(0, 0%, 96%, 1);\n  --md-code-hl-color: hsla(60, 100%, 50%, 0.5);\n  --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n  --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n  --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n  --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n  --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n  --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n  --md-code-hl-name-color: var(--md-code-fg-color);\n  --md-code-hl-operator-color: var(--md-default-fg-color--light);\n  --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n  --md-code-hl-comment-color: var(--md-default-fg-color--light);\n  --md-code-hl-generic-color: var(--md-default-fg-color--light);\n  --md-code-hl-variable-color: var(--md-default-fg-color--light);\n  --md-typeset-color: var(--md-default-fg-color);\n  --md-typeset-a-color: var(--md-primary-fg-color);\n  --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5);\n  --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n  --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n  --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n  --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n  --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n  --md-admonition-fg-color: var(--md-default-fg-color);\n  --md-admonition-bg-color: var(--md-default-bg-color);\n  --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n  --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n}\n\n.md-icon svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family: var(--md-text-font-family, _), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family: var(--md-code-font-family, _), SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n.md-typeset {\n  font-size: 0.8rem;\n  line-height: 1.6;\n  color-adjust: exact;\n}\n@media print {\n  .md-typeset {\n    font-size: 0.68rem;\n  }\n}\n.md-typeset ul,\n.md-typeset ol,\n.md-typeset dl,\n.md-typeset figure,\n.md-typeset blockquote,\n.md-typeset pre {\n  display: flow-root;\n  margin: 1em 0;\n}\n.md-typeset h1 {\n  margin: 0 0 1.25em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 300;\n  font-size: 2em;\n  line-height: 1.3;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 {\n  margin: 1.6em 0 0.64em;\n  font-weight: 300;\n  font-size: 1.5625em;\n  line-height: 1.4;\n  letter-spacing: -0.01em;\n}\n.md-typeset h3 {\n  margin: 1.6em 0 0.8em;\n  font-weight: 400;\n  font-size: 1.25em;\n  line-height: 1.5;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 + h3 {\n  margin-top: 0.8em;\n}\n.md-typeset h4 {\n  margin: 1em 0;\n  font-weight: 700;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5,\n.md-typeset h6 {\n  margin: 1.25em 0;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.8em;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5 {\n  text-transform: uppercase;\n}\n.md-typeset hr {\n  display: flow-root;\n  margin: 1.5em 0;\n  border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset a {\n  color: var(--md-typeset-a-color);\n  word-break: break-word;\n}\n.md-typeset a, .md-typeset a::before {\n  transition: color 125ms;\n}\n.md-typeset a:focus, .md-typeset a:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n  color: var(--md-code-fg-color);\n  direction: ltr;\n}\n@media print {\n  .md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n    white-space: pre-wrap;\n  }\n}\n.md-typeset code {\n  padding: 0 0.2941176471em;\n  font-size: 0.85em;\n  word-break: break-word;\n  background-color: var(--md-code-bg-color);\n  border-radius: 0.1rem;\n  box-decoration-break: clone;\n}\n.md-typeset code:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset h1 code,\n.md-typeset h2 code,\n.md-typeset h3 code,\n.md-typeset h4 code,\n.md-typeset h5 code,\n.md-typeset h6 code {\n  margin: initial;\n  padding: initial;\n  background-color: transparent;\n  box-shadow: none;\n}\n.md-typeset a > code {\n  color: currentColor;\n}\n.md-typeset pre {\n  position: relative;\n  line-height: 1.4;\n}\n.md-typeset pre > code {\n  display: block;\n  margin: 0;\n  padding: 0.7720588235em 1.1764705882em;\n  overflow: auto;\n  word-break: normal;\n  box-shadow: none;\n  box-decoration-break: slice;\n  touch-action: auto;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-typeset pre > code:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-typeset pre > code::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > pre {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > pre code {\n    border-radius: 0;\n  }\n}\n.md-typeset kbd {\n  display: inline-block;\n  padding: 0 0.6666666667em;\n  color: var(--md-default-fg-color);\n  font-size: 0.75em;\n  vertical-align: text-top;\n  word-break: break-word;\n  background-color: var(--md-typeset-kbd-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.1rem 0 0.05rem var(--md-typeset-kbd-border-color), 0 0.1rem 0 var(--md-typeset-kbd-border-color), 0 -0.1rem 0.2rem var(--md-typeset-kbd-accent-color) inset;\n}\n.md-typeset mark {\n  color: inherit;\n  word-break: break-word;\n  background-color: var(--md-typeset-mark-color);\n  box-decoration-break: clone;\n}\n.md-typeset abbr {\n  text-decoration: none;\n  border-bottom: 0.05rem dotted var(--md-default-fg-color--light);\n  cursor: help;\n}\n@media (hover: none) {\n  .md-typeset abbr {\n    position: relative;\n  }\n  .md-typeset abbr[title]:focus::after, .md-typeset abbr[title]:hover::after {\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n    position: absolute;\n    left: 0;\n    display: inline-block;\n    width: auto;\n    min-width: max-content;\n    max-width: 80%;\n    margin-top: 2em;\n    padding: 0.2rem 0.3rem;\n    color: var(--md-default-bg-color);\n    font-size: 0.7rem;\n    background-color: var(--md-default-fg-color);\n    border-radius: 0.1rem;\n    content: attr(title);\n  }\n}\n.md-typeset small {\n  opacity: 0.75;\n}\n.md-typeset sup,\n.md-typeset sub {\n  margin-left: 0.078125em;\n}\n[dir=rtl] .md-typeset sup,\n[dir=rtl] .md-typeset sub {\n  margin-right: 0.078125em;\n  margin-left: initial;\n}\n.md-typeset blockquote {\n  padding-left: 0.6rem;\n  color: var(--md-default-fg-color--light);\n  border-left: 0.2rem solid var(--md-default-fg-color--lighter);\n}\n[dir=rtl] .md-typeset blockquote {\n  padding-right: 0.6rem;\n  padding-left: initial;\n  border-right: 0.2rem solid var(--md-default-fg-color--lighter);\n  border-left: initial;\n}\n.md-typeset ul {\n  list-style-type: disc;\n}\n.md-typeset ul,\n.md-typeset ol {\n  margin-left: 0.625em;\n  padding: 0;\n}\n[dir=rtl] .md-typeset ul,\n[dir=rtl] .md-typeset ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset ul ol,\n.md-typeset ol ol {\n  list-style-type: lower-alpha;\n}\n.md-typeset ul ol ol,\n.md-typeset ol ol ol {\n  list-style-type: lower-roman;\n}\n.md-typeset ul li,\n.md-typeset ol li {\n  margin-bottom: 0.5em;\n  margin-left: 1.25em;\n}\n[dir=rtl] .md-typeset ul li,\n[dir=rtl] .md-typeset ol li {\n  margin-right: 1.25em;\n  margin-left: initial;\n}\n.md-typeset ul li p,\n.md-typeset ul li blockquote,\n.md-typeset ol li p,\n.md-typeset ol li blockquote {\n  margin: 0.5em 0;\n}\n.md-typeset ul li:last-child,\n.md-typeset ol li:last-child {\n  margin-bottom: 0;\n}\n.md-typeset ul li ul,\n.md-typeset ul li ol,\n.md-typeset ol li ul,\n.md-typeset ol li ol {\n  margin: 0.5em 0 0.5em 0.625em;\n}\n[dir=rtl] .md-typeset ul li ul,\n[dir=rtl] .md-typeset ul li ol,\n[dir=rtl] .md-typeset ol li ul,\n[dir=rtl] .md-typeset ol li ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset dd {\n  margin: 1em 0 1.5em 1.875em;\n}\n[dir=rtl] .md-typeset dd {\n  margin-right: 1.875em;\n  margin-left: initial;\n}\n.md-typeset img,\n.md-typeset svg {\n  max-width: 100%;\n  height: auto;\n}\n.md-typeset img[align=left],\n.md-typeset svg[align=left] {\n  margin: 1em;\n  margin-left: 0;\n}\n.md-typeset img[align=right],\n.md-typeset svg[align=right] {\n  margin: 1em;\n  margin-right: 0;\n}\n.md-typeset img[align]:only-child,\n.md-typeset svg[align]:only-child {\n  margin-top: 0;\n}\n.md-typeset figure {\n  width: fit-content;\n  max-width: 100%;\n  margin: 0 auto;\n  text-align: center;\n}\n.md-typeset figure img {\n  display: block;\n}\n.md-typeset figcaption {\n  max-width: 24rem;\n  margin: 1em auto 2em;\n  font-style: italic;\n}\n.md-typeset iframe {\n  max-width: 100%;\n}\n.md-typeset table:not([class]) {\n  display: inline-block;\n  max-width: 100%;\n  overflow: auto;\n  font-size: 0.64rem;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);\n  touch-action: auto;\n}\n@media print {\n  .md-typeset table:not([class]) {\n    display: table;\n  }\n}\n.md-typeset table:not([class]) + * {\n  margin-top: 1.5em;\n}\n.md-typeset table:not([class]) th > *:first-child,\n.md-typeset table:not([class]) td > *:first-child {\n  margin-top: 0;\n}\n.md-typeset table:not([class]) th > *:last-child,\n.md-typeset table:not([class]) td > *:last-child {\n  margin-bottom: 0;\n}\n.md-typeset table:not([class]) th:not([align]),\n.md-typeset table:not([class]) td:not([align]) {\n  text-align: left;\n}\n[dir=rtl] .md-typeset table:not([class]) th:not([align]),\n[dir=rtl] .md-typeset table:not([class]) td:not([align]) {\n  text-align: right;\n}\n.md-typeset table:not([class]) th {\n  min-width: 5rem;\n  padding: 0.9375em 1.25em;\n  color: var(--md-default-bg-color);\n  vertical-align: top;\n  background-color: var(--md-default-fg-color--light);\n}\n.md-typeset table:not([class]) th a {\n  color: inherit;\n}\n.md-typeset table:not([class]) td {\n  padding: 0.9375em 1.25em;\n  vertical-align: top;\n  border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset table:not([class]) tr {\n  transition: background-color 125ms;\n}\n.md-typeset table:not([class]) tr:hover {\n  background-color: rgba(0, 0, 0, 0.035);\n  box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;\n}\n.md-typeset table:not([class]) tr:first-child td {\n  border-top: 0;\n}\n.md-typeset table:not([class]) a {\n  word-break: normal;\n}\n.md-typeset table th[role=columnheader] {\n  cursor: pointer;\n}\n.md-typeset table th[role=columnheader]::after {\n  display: inline-block;\n  width: 1.2em;\n  height: 1.2em;\n  margin-left: 0.5em;\n  vertical-align: sub;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-typeset table th[role=columnheader][aria-sort=ascending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--ascending);\n}\n.md-typeset table th[role=columnheader][aria-sort=descending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--descending);\n}\n.md-typeset__scrollwrap {\n  margin: 1em -0.8rem;\n  overflow-x: auto;\n  touch-action: auto;\n}\n.md-typeset__table {\n  display: inline-block;\n  margin-bottom: 0.5em;\n  padding: 0 0.8rem;\n}\n@media print {\n  .md-typeset__table {\n    display: block;\n  }\n}\nhtml .md-typeset__table table {\n  display: table;\n  width: 100%;\n  margin: 0;\n  overflow: hidden;\n}\n\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  font-size: 125%;\n}\n@media screen and (min-width: 100em) {\n  html {\n    font-size: 137.5%;\n  }\n}\n@media screen and (min-width: 125em) {\n  html {\n    font-size: 150%;\n  }\n}\n\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  font-size: 0.5rem;\n  background-color: var(--md-default-bg-color);\n}\n@media print {\n  body {\n    display: block;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  body[data-md-state=lock] {\n    position: fixed;\n  }\n}\n\n.md-grid {\n  max-width: 61rem;\n  margin-right: auto;\n  margin-left: auto;\n}\n\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n}\n@media print {\n  .md-container {\n    display: block;\n  }\n}\n\n.md-main {\n  flex-grow: 1;\n}\n.md-main__inner {\n  display: flex;\n  height: 100%;\n  margin-top: 1.5rem;\n}\n\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.md-toggle {\n  display: none;\n}\n\n.md-skip {\n  position: fixed;\n  z-index: -1;\n  margin: 0.5rem;\n  padding: 0.3rem 0.5rem;\n  color: var(--md-default-bg-color);\n  font-size: 0.64rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(0.4rem);\n  opacity: 0;\n}\n.md-skip:focus {\n  z-index: 10;\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 175ms 75ms;\n}\n\n@page {\n  margin: 25mm;\n}\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-announce {\n    display: none;\n  }\n}\n.md-announce__inner {\n  margin: 0.6rem auto;\n  padding: 0 0.8rem;\n  color: var(--md-footer-fg-color);\n  font-size: 0.7rem;\n}\n\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n.md-clipboard {\n  position: absolute;\n  top: 0.5em;\n  right: 0.5em;\n  z-index: 1;\n  width: 1.5em;\n  height: 1.5em;\n  color: var(--md-default-fg-color--lightest);\n  border-radius: 0.1rem;\n  cursor: pointer;\n  transition: color 250ms;\n}\n@media print {\n  .md-clipboard {\n    display: none;\n  }\n}\n.md-clipboard:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n:hover > .md-clipboard {\n  color: var(--md-default-fg-color--light);\n}\n.md-clipboard:focus, .md-clipboard:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-clipboard::after {\n  display: block;\n  width: 1.125em;\n  height: 1.125em;\n  margin: 0 auto;\n  background-color: currentColor;\n  mask-image: var(--md-clipboard-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-clipboard--inline {\n  cursor: pointer;\n}\n.md-clipboard--inline code {\n  transition: color 250ms, background-color 250ms;\n}\n.md-clipboard--inline:focus code, .md-clipboard--inline:hover code {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n\n.md-content {\n  flex-grow: 1;\n  overflow: hidden;\n  scroll-padding-top: 51.2rem;\n}\n.md-content__inner {\n  margin: 0 0.8rem 1.2rem;\n  padding-top: 0.6rem;\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-left: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n    margin-left: 0.8rem;\n  }\n  .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 0.8rem;\n    margin-left: 1.2rem;\n  }\n}\n.md-content__inner::before {\n  display: block;\n  height: 0.4rem;\n  content: \"\";\n}\n.md-content__inner > :last-child {\n  margin-bottom: 0;\n}\n.md-content__button {\n  float: right;\n  margin: 0.4rem 0;\n  margin-left: 0.4rem;\n  padding: 0;\n}\n@media print {\n  .md-content__button {\n    display: none;\n  }\n}\n[dir=rtl] .md-content__button {\n  float: left;\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n[dir=rtl] .md-content__button svg {\n  transform: scaleX(-1);\n}\n.md-typeset .md-content__button {\n  color: var(--md-default-fg-color--lighter);\n}\n.md-content__button svg {\n  display: inline;\n  vertical-align: top;\n}\n\n.md-dialog {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n  position: fixed;\n  right: 0.8rem;\n  bottom: 0.8rem;\n  left: initial;\n  z-index: 2;\n  min-width: 11.1rem;\n  padding: 0.4rem 0.6rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(100%);\n  opacity: 0;\n  transition: transform 0ms 400ms, opacity 400ms;\n  pointer-events: none;\n}\n@media print {\n  .md-dialog {\n    display: none;\n  }\n}\n[dir=rtl] .md-dialog {\n  right: initial;\n  left: 0.8rem;\n}\n.md-dialog[data-md-state=open] {\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1), opacity 400ms;\n  pointer-events: initial;\n}\n.md-dialog__inner {\n  color: var(--md-default-bg-color);\n  font-size: 0.7rem;\n}\n\n.md-typeset .md-button {\n  display: inline-block;\n  padding: 0.625em 2em;\n  color: var(--md-primary-fg-color);\n  font-weight: 700;\n  border: 0.1rem solid currentColor;\n  border-radius: 0.1rem;\n  transition: color 125ms, background-color 125ms, border-color 125ms;\n}\n.md-typeset .md-button--primary {\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  border-color: var(--md-primary-fg-color);\n}\n.md-typeset .md-button:focus, .md-typeset .md-button:hover {\n  color: var(--md-accent-bg-color);\n  background-color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .md-input {\n  height: 1.8rem;\n  padding: 0 0.6rem;\n  font-size: 0.8rem;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transition: box-shadow 250ms;\n}\n.md-typeset .md-input:focus, .md-typeset .md-input:hover {\n  box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.15), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.15);\n}\n.md-typeset .md-input--stretch {\n  width: 100%;\n}\n\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0), 0 0.2rem 0.4rem rgba(0, 0, 0, 0);\n  transition: color 250ms, background-color 250ms;\n}\n@media print {\n  .md-header {\n    display: none;\n  }\n}\n.md-header[data-md-state=shadow] {\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), color 250ms, background-color 250ms, box-shadow 250ms;\n}\n.md-header[data-md-state=hidden] {\n  transform: translateY(-100%);\n  transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), color 250ms, background-color 250ms, box-shadow 250ms;\n}\n.md-header__inner {\n  display: flex;\n  align-items: center;\n  padding: 0 0.2rem;\n}\n.md-header__button {\n  position: relative;\n  z-index: 1;\n  display: inline-block;\n  margin: 0.2rem;\n  padding: 0.4rem;\n  color: currentColor;\n  vertical-align: middle;\n  cursor: pointer;\n  transition: opacity 250ms;\n}\n.md-header__button:focus, .md-header__button:hover {\n  opacity: 0.7;\n}\n.md-header__button:not(.focus-visible) {\n  outline: none;\n}\n.md-header__button.md-logo {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-header__button.md-logo {\n    display: none;\n  }\n}\n.md-header__button.md-logo img,\n.md-header__button.md-logo svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n@media screen and (min-width: 60em) {\n  .md-header__button[for=__search] {\n    display: none;\n  }\n}\n.no-js .md-header__button[for=__search] {\n  display: none;\n}\n[dir=rtl] .md-header__button[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__button[for=__drawer] {\n    display: none;\n  }\n}\n.md-header__topic {\n  position: absolute;\n  display: flex;\n  max-width: 100%;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n}\n.md-header__topic + .md-header__topic {\n  z-index: -1;\n  transform: translateX(1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__topic + .md-header__topic {\n  transform: translateX(-1.25rem);\n}\n.md-header__title {\n  flex-grow: 1;\n  height: 2.4rem;\n  margin-right: 0.4rem;\n  margin-left: 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-header__title[data-md-state=active] .md-header__topic {\n  z-index: -1;\n  transform: translateX(-1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic {\n  transform: translateX(1.25rem);\n}\n.md-header__title[data-md-state=active] .md-header__topic + .md-header__topic {\n  z-index: 0;\n  transform: translateX(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: initial;\n}\n.md-header__title > .md-header__ellipsis {\n  position: relative;\n  width: 100%;\n  height: 100%;\n}\n.md-header__options {\n  display: flex;\n  flex-shrink: 0;\n  max-width: 100%;\n  white-space: nowrap;\n  transition: max-width 0ms 250ms, opacity 250ms 250ms;\n}\n.md-header__options > [data-md-state=hidden] {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-header__options {\n  max-width: 0;\n  opacity: 0;\n  transition: max-width 0ms, opacity 0ms;\n}\n.md-header__source {\n  display: none;\n}\n@media screen and (min-width: 60em) {\n  .md-header__source {\n    display: block;\n    width: 11.7rem;\n    max-width: 11.7rem;\n    margin-left: 1rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1rem;\n    margin-left: initial;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__source {\n    margin-left: 1.4rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1.4rem;\n  }\n}\n\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-footer {\n    display: none;\n  }\n}\n.md-footer__inner {\n  padding: 0.2rem;\n  overflow: auto;\n}\n.md-footer__link {\n  display: flex;\n  padding-top: 1.4rem;\n  padding-bottom: 0.4rem;\n  transition: opacity 250ms;\n}\n@media screen and (min-width: 45em) {\n  .md-footer__link {\n    width: 50%;\n  }\n}\n.md-footer__link:focus, .md-footer__link:hover {\n  opacity: 0.7;\n}\n.md-footer__link--prev {\n  float: left;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--prev {\n    width: 25%;\n  }\n  .md-footer__link--prev .md-footer__title {\n    display: none;\n  }\n}\n[dir=rtl] .md-footer__link--prev {\n  float: right;\n}\n[dir=rtl] .md-footer__link--prev svg {\n  transform: scaleX(-1);\n}\n.md-footer__link--next {\n  float: right;\n  text-align: right;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--next {\n    width: 75%;\n  }\n}\n[dir=rtl] .md-footer__link--next {\n  float: left;\n  text-align: left;\n}\n[dir=rtl] .md-footer__link--next svg {\n  transform: scaleX(-1);\n}\n.md-footer__title {\n  position: relative;\n  flex-grow: 1;\n  max-width: calc(100% - 2.4rem);\n  padding: 0 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-footer__button {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n.md-footer__direction {\n  position: absolute;\n  right: 0;\n  left: 0;\n  margin-top: -1rem;\n  padding: 0 1rem;\n  font-size: 0.64rem;\n  opacity: 0.7;\n}\n\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n}\n.md-footer-meta__inner {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  padding: 0.2rem;\n}\nhtml .md-footer-meta.md-typeset a {\n  color: var(--md-footer-fg-color--light);\n}\nhtml .md-footer-meta.md-typeset a:focus, html .md-footer-meta.md-typeset a:hover {\n  color: var(--md-footer-fg-color);\n}\n\n.md-footer-copyright {\n  width: 100%;\n  margin: auto 0.6rem;\n  padding: 0.4rem 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: 0.64rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-copyright {\n    width: auto;\n  }\n}\n.md-footer-copyright__highlight {\n  color: var(--md-footer-fg-color--light);\n}\n\n.md-footer-social {\n  margin: 0 0.4rem;\n  padding: 0.2rem 0 0.6rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-social {\n    padding: 0.6rem 0;\n  }\n}\n.md-footer-social__link {\n  display: inline-block;\n  width: 1.6rem;\n  height: 1.6rem;\n  text-align: center;\n}\n.md-footer-social__link::before {\n  line-height: 1.9;\n}\n.md-footer-social__link svg {\n  max-height: 0.8rem;\n  vertical-align: -25%;\n  fill: currentColor;\n}\n\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n.md-nav {\n  font-size: 0.7rem;\n  line-height: 1.3;\n}\n.md-nav__title {\n  display: block;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n}\n.md-nav__title .md-nav__button {\n  display: none;\n}\n.md-nav__title .md-nav__button img {\n  width: auto;\n  height: 100%;\n}\n.md-nav__title .md-nav__button.md-logo img,\n.md-nav__title .md-nav__button.md-logo svg {\n  display: block;\n  width: 2.4rem;\n  height: 2.4rem;\n  fill: currentColor;\n}\n.md-nav__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-nav__item {\n  padding: 0 0.6rem;\n}\n.md-nav__item .md-nav__item {\n  padding-right: 0;\n}\n[dir=rtl] .md-nav__item .md-nav__item {\n  padding-right: 0.6rem;\n  padding-left: 0;\n}\n.md-nav__link {\n  display: block;\n  margin-top: 0.625em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  cursor: pointer;\n  transition: color 125ms;\n  scroll-snap-align: start;\n}\n.md-nav__link[data-md-state=blur] {\n  color: var(--md-default-fg-color--light);\n}\n.md-nav__item .md-nav__link--active {\n  color: var(--md-typeset-a-color);\n}\n.md-nav__item--nested > .md-nav__link {\n  color: inherit;\n}\n.md-nav__link:focus, .md-nav__link:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-nav--primary .md-nav__link[for=__toc] {\n  display: none;\n}\n.md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n  display: block;\n  width: 100%;\n  height: 100%;\n  mask-image: var(--md-toc-icon);\n  background-color: currentColor;\n}\n.md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n  display: none;\n}\n.md-nav__source {\n  display: none;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-nav--primary, .md-nav--primary .md-nav {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: 1;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n  }\n  .md-nav--primary .md-nav__title,\n.md-nav--primary .md-nav__item {\n    font-size: 0.8rem;\n    line-height: 1.5;\n  }\n  .md-nav--primary .md-nav__title {\n    position: relative;\n    height: 5.6rem;\n    padding: 3rem 0.8rem 0.2rem;\n    color: var(--md-default-fg-color--light);\n    font-weight: 400;\n    line-height: 2.4rem;\n    white-space: nowrap;\n    background-color: var(--md-default-fg-color--lightest);\n    cursor: pointer;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    position: absolute;\n    top: 0.4rem;\n    left: 0.4rem;\n    display: block;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin: 0.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon {\n    right: 0.4rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--prev);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list {\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0.05rem 0 var(--md-default-fg-color--lightest) inset;\n    scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child {\n    border-top: 0;\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color);\n  }\n  .md-nav--primary .md-nav__title .md-logo {\n    position: absolute;\n    top: 0.2rem;\n    left: 0.2rem;\n    display: block;\n    margin: 0.2rem;\n    padding: 0.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-logo {\n    right: 0.2rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__list {\n    flex: 1;\n  }\n  .md-nav--primary .md-nav__item {\n    padding: 0;\n    border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n  }\n  .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link {\n    color: var(--md-typeset-a-color);\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link:focus, .md-nav--primary .md-nav__item--active > .md-nav__link:hover {\n    color: var(--md-accent-fg-color);\n  }\n  .md-nav--primary .md-nav__link {\n    position: relative;\n    margin-top: 0;\n    padding: 0.6rem 0.8rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon {\n    position: absolute;\n    top: 50%;\n    right: 0.6rem;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin-top: -0.6rem;\n    color: inherit;\n    font-size: 1.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon {\n    right: initial;\n    left: 0.6rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  [dir=rtl] .md-nav--primary .md-nav__icon::after {\n    transform: scale(-1);\n  }\n  .md-nav--primary .md-nav--secondary .md-nav__link {\n    position: static;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav {\n    position: static;\n    background-color: transparent;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-left: 1.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-right: 1.4rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-left: 2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-right: 2rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 2.6rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 2.6rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 3.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 3.2rem;\n    padding-left: initial;\n  }\n  .md-nav--secondary {\n    background-color: transparent;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: flex;\n    transform: translateX(100%);\n    opacity: 0;\n    transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), opacity 125ms 50ms;\n  }\n  [dir=rtl] .md-nav__toggle ~ .md-nav {\n    transform: translateX(-100%);\n  }\n  .md-nav__toggle:checked ~ .md-nav {\n    transform: translateX(0);\n    opacity: 1;\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 125ms 125ms;\n  }\n  .md-nav__toggle:checked ~ .md-nav > .md-nav__list {\n    backface-visibility: hidden;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  .md-nav--primary .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n  .md-nav__source {\n    display: block;\n    padding: 0 0.2rem;\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color--dark);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-nav--integrated .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n    scroll-snap-align: initial;\n  }\n  [dir=rtl] .md-nav--integrated .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-nav--secondary .md-nav__title[for=__toc] {\n    scroll-snap-align: start;\n  }\n  .md-nav--secondary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-nav {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    scroll-snap-align: start;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: none;\n  }\n  .md-nav__toggle:checked ~ .md-nav, .md-nav__toggle:indeterminate ~ .md-nav {\n    display: block;\n  }\n  .md-nav__item--nested > .md-nav > .md-nav__title {\n    display: none;\n  }\n  .md-nav__item--section {\n    display: block;\n    margin: 1.25em 0;\n  }\n  .md-nav__item--section:last-child {\n    margin-bottom: 0;\n  }\n  .md-nav__item--section > .md-nav__link {\n    display: none;\n  }\n  .md-nav__item--section > .md-nav {\n    display: block;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__list > .md-nav__item {\n    padding: 0;\n  }\n  .md-nav__icon {\n    float: right;\n    width: 0.9rem;\n    height: 0.9rem;\n    transition: transform 250ms;\n  }\n  [dir=rtl] .md-nav__icon {\n    float: left;\n    transform: rotate(180deg);\n  }\n  .md-nav__icon::after {\n    display: inline-block;\n    width: 100%;\n    height: 100%;\n    vertical-align: -0.1rem;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon, .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link .md-nav__icon {\n    transform: rotate(90deg);\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--nested,\n.md-nav--lifted > .md-nav__title {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active {\n    display: block;\n    padding: 0;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0 0.6rem;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav__item {\n    padding-right: 0.6rem;\n  }\n  .md-nav--lifted .md-nav[data-md-level=\"1\"] {\n    display: block;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: block;\n    margin-bottom: 1.25em;\n    border-left: 0.05rem solid var(--md-primary-fg-color);\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav > .md-nav__title {\n    display: none;\n  }\n}\n\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n.md-search {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search {\n    padding: 0.2rem 0;\n  }\n}\n.no-js .md-search {\n  display: none;\n}\n.md-search__overlay {\n  z-index: 1;\n  opacity: 0;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__overlay {\n    position: absolute;\n    top: 0.2rem;\n    left: -2.2rem;\n    width: 2rem;\n    height: 2rem;\n    overflow: hidden;\n    background-color: var(--md-default-bg-color);\n    border-radius: 1rem;\n    transform-origin: center;\n    transition: transform 300ms 100ms, opacity 200ms 200ms;\n    pointer-events: none;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: -2.2rem;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    opacity: 1;\n    transition: transform 400ms, opacity 100ms;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    cursor: pointer;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: 0;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    width: 100%;\n    height: 200vh;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@media screen and (max-width: 29.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(45);\n  }\n}\n@media screen and (min-width: 30em) and (max-width: 44.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(60);\n  }\n}\n@media screen and (min-width: 45em) and (max-width: 59.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(75);\n  }\n}\n.md-search__inner {\n  backface-visibility: hidden;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__inner {\n    position: fixed;\n    top: 0;\n    left: 100%;\n    z-index: 2;\n    width: 100%;\n    height: 100%;\n    transform: translateX(5%);\n    opacity: 0;\n    transition: right 0ms 300ms, left 0ms 300ms, transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms 150ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    left: 0;\n    transform: translateX(0);\n    opacity: 1;\n    transition: right 0ms 0ms, left 0ms 0ms, transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms 150ms;\n  }\n  [dir=rtl] [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    right: 0;\n    left: initial;\n  }\n  html [dir=rtl] .md-search__inner {\n    right: 100%;\n    left: initial;\n    transform: translateX(-5%);\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__inner {\n    position: relative;\n    float: right;\n    width: 11.7rem;\n    padding: 0.1rem 0;\n    transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n  }\n  [dir=rtl] .md-search__inner {\n    float: left;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 34.4rem;\n  }\n}\n.md-search__form {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search__form {\n    border-radius: 0.1rem;\n  }\n}\n.md-search__input {\n  position: relative;\n  z-index: 2;\n  padding: 0 2.2rem 0 3.6rem;\n  text-overflow: ellipsis;\n  background-color: var(--md-default-bg-color);\n  box-shadow: 0 0 0.6rem transparent;\n  transition: color 250ms, background-color 250ms, box-shadow 250ms;\n}\n[dir=rtl] .md-search__input {\n  padding: 0 3.6rem 0 2.2rem;\n}\n.md-search__input::placeholder {\n  transition: color 250ms;\n}\n.md-search__input ~ .md-search__icon, .md-search__input::placeholder {\n  color: var(--md-default-fg-color--light);\n}\n.md-search__input::-ms-clear {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input {\n  box-shadow: 0 0 0.6rem rgba(0, 0, 0, 0.07);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__input {\n    width: 100%;\n    height: 2.4rem;\n    font-size: 0.9rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__input {\n    width: 100%;\n    height: 1.8rem;\n    padding-left: 2.2rem;\n    color: inherit;\n    font-size: 0.8rem;\n    background-color: rgba(0, 0, 0, 0.26);\n    border-radius: 0.1rem;\n  }\n  [dir=rtl] .md-search__input {\n    padding-right: 2.2rem;\n  }\n  .md-search__input + .md-search__icon {\n    color: var(--md-primary-bg-color);\n  }\n  .md-search__input::placeholder {\n    color: var(--md-primary-bg-color--light);\n  }\n  .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input {\n    color: var(--md-default-fg-color);\n    text-overflow: clip;\n    background-color: var(--md-default-bg-color);\n    border-radius: 0.1rem 0.1rem 0 0;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle=search]:checked ~ .md-header .md-search__input::placeholder {\n    color: var(--md-default-fg-color--light);\n  }\n}\n.md-search__icon {\n  position: absolute;\n  z-index: 2;\n  width: 1.2rem;\n  height: 1.2rem;\n  cursor: pointer;\n  transition: color 250ms, opacity 250ms;\n}\n.md-search__icon:hover {\n  opacity: 0.7;\n}\n.md-search__icon[for=__search] {\n  top: 0.3rem;\n  left: 0.5rem;\n}\n[dir=rtl] .md-search__icon[for=__search] {\n  right: 0.5rem;\n  left: initial;\n}\n[dir=rtl] .md-search__icon[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[for=__search] {\n    top: 0.6rem;\n    left: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[for=__search] {\n    right: 0.8rem;\n    left: initial;\n  }\n  .md-search__icon[for=__search] svg:first-child {\n    display: none;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__icon[for=__search] {\n    pointer-events: none;\n  }\n  .md-search__icon[for=__search] svg:last-child {\n    display: none;\n  }\n}\n.md-search__icon[type=reset] {\n  top: 0.3rem;\n  right: 0.5rem;\n  transform: scale(0.75);\n  opacity: 0;\n  transition: transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-search__icon[type=reset] {\n  right: initial;\n  left: 0.5rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[type=reset] {\n    top: 0.6rem;\n    right: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[type=reset] {\n    right: initial;\n    left: 0.8rem;\n  }\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset] {\n  transform: scale(1);\n  opacity: 1;\n  pointer-events: initial;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset]:hover {\n  opacity: 0.7;\n}\n.md-search__output {\n  position: absolute;\n  z-index: 1;\n  width: 100%;\n  overflow: hidden;\n  border-radius: 0 0 0.1rem 0.1rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__output {\n    top: 2.4rem;\n    bottom: 0;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__output {\n    top: 1.9rem;\n    opacity: 0;\n    transition: opacity 400ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__output {\n    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.4);\n    opacity: 1;\n  }\n}\n.md-search__scrollwrap {\n  height: 100%;\n  overflow-y: auto;\n  background-color: var(--md-default-bg-color);\n  backface-visibility: hidden;\n  touch-action: pan-y;\n}\n@media (max-resolution: 1dppx) {\n  .md-search__scrollwrap {\n    transform: translateZ(0);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search__scrollwrap {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-search__scrollwrap {\n    width: 34.4rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__scrollwrap {\n    max-height: 0;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__scrollwrap {\n    max-height: 75vh;\n  }\n  .md-search__scrollwrap:hover {\n    scrollbar-color: var(--md-accent-fg-color) transparent;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar {\n    width: 0.2rem;\n    height: 0.2rem;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb {\n    background-color: var(--md-default-fg-color--lighter);\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb:hover {\n    background-color: var(--md-accent-fg-color);\n  }\n}\n\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n}\n.md-search-result__meta {\n  padding: 0 0.8rem;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.8rem;\n  background-color: var(--md-default-fg-color--lightest);\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__meta {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__meta {\n    padding-right: 2.2rem;\n    padding-left: initial;\n  }\n}\n.md-search-result__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-search-result__item {\n  box-shadow: 0 -0.05rem 0 var(--md-default-fg-color--lightest);\n}\n.md-search-result__item:first-child {\n  box-shadow: none;\n}\n.md-search-result__link {\n  display: block;\n  outline: none;\n  transition: background-color 250ms;\n  scroll-snap-align: start;\n}\n.md-search-result__link:focus, .md-search-result__link:hover {\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__link:last-child p:last-child {\n  margin-bottom: 0.6rem;\n}\n.md-search-result__more summary {\n  display: block;\n  padding: 0.75em 0.8rem;\n  color: var(--md-typeset-a-color);\n  font-size: 0.64rem;\n  outline: 0;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__more summary {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__more summary {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__more summary:focus, .md-search-result__more summary:hover {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__more summary::marker, .md-search-result__more summary::-webkit-details-marker {\n  display: none;\n}\n.md-search-result__more summary ~ * > * {\n  opacity: 0.65;\n}\n.md-search-result__article {\n  position: relative;\n  padding: 0 0.8rem;\n  overflow: hidden;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__article {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__article {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__article--document .md-search-result__title {\n  margin: 0.55rem 0;\n  font-weight: 400;\n  font-size: 0.8rem;\n  line-height: 1.4;\n}\n.md-search-result__icon {\n  position: absolute;\n  left: 0;\n  width: 1.2rem;\n  height: 1.2rem;\n  margin: 0.5rem;\n  color: var(--md-default-fg-color--light);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search-result__icon {\n    display: none;\n  }\n}\n.md-search-result__icon::after {\n  display: inline-block;\n  width: 100%;\n  height: 100%;\n  background-color: currentColor;\n  mask-image: var(--md-search-result-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-search-result__icon {\n  right: 0;\n  left: initial;\n}\n[dir=rtl] .md-search-result__icon::after {\n  transform: scaleX(-1);\n}\n.md-search-result__title {\n  margin: 0.5em 0;\n  font-weight: 700;\n  font-size: 0.64rem;\n  line-height: 1.6;\n}\n.md-search-result__teaser {\n  display: -webkit-box;\n  max-height: 2rem;\n  margin: 0.5em 0;\n  overflow: hidden;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.6;\n  text-overflow: ellipsis;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n.md-search-result__teaser mark {\n  text-decoration: underline;\n  background-color: transparent;\n}\n.md-search-result__terms {\n  margin: 0.5em 0;\n  font-size: 0.64rem;\n  font-style: italic;\n}\n.md-search-result mark {\n  color: var(--md-accent-fg-color);\n  background-color: transparent;\n}\n\n.md-select {\n  position: relative;\n  z-index: 1;\n}\n.md-select__inner {\n  position: absolute;\n  top: calc(100% - 0.2rem);\n  left: 50%;\n  max-height: 0;\n  margin-top: 0.2rem;\n  color: var(--md-default-fg-color);\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  transform: translate3d(-50%, 0.3rem, 0);\n  opacity: 0;\n  transition: transform 250ms 375ms, opacity 250ms 250ms, max-height 0ms 500ms;\n}\n.md-select:focus-within .md-select__inner, .md-select:hover .md-select__inner {\n  max-height: 10rem;\n  transform: translate3d(-50%, 0, 0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms, max-height 250ms;\n}\n.md-select__inner::after {\n  position: absolute;\n  top: 0;\n  left: 50%;\n  width: 0;\n  height: 0;\n  margin-top: -0.2rem;\n  margin-left: -0.2rem;\n  border: 0.2rem solid transparent;\n  border-top: 0;\n  border-bottom-color: var(--md-default-bg-color);\n  content: \"\";\n}\n.md-select__list {\n  max-height: inherit;\n  margin: 0;\n  padding: 0;\n  overflow: auto;\n  font-size: 0.8rem;\n  list-style-type: none;\n  border-radius: 0.1rem;\n}\n.md-select__item {\n  line-height: 1.8rem;\n}\n.md-select__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  cursor: pointer;\n  transition: background-color 250ms, color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-select__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-select__link:focus, .md-select__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n.md-sidebar {\n  position: sticky;\n  top: 2.4rem;\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: 12.1rem;\n  padding: 1.2rem 0;\n}\n@media print {\n  .md-sidebar {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-sidebar--primary {\n    position: fixed;\n    top: 0;\n    left: -12.1rem;\n    z-index: 3;\n    display: block;\n    width: 12.1rem;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n    transform: translateX(0);\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms;\n  }\n  [dir=rtl] .md-sidebar--primary {\n    right: -12.1rem;\n    left: initial;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);\n    transform: translateX(12.1rem);\n  }\n  [dir=rtl] [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    transform: translateX(-12.1rem);\n  }\n  .md-sidebar--primary .md-sidebar__scrollwrap {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    margin: 0;\n    scroll-snap-type: none;\n    overflow: hidden;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar {\n    height: 0;\n  }\n  .no-js .md-sidebar {\n    height: auto;\n  }\n}\n.md-sidebar--secondary {\n  display: none;\n  order: 2;\n}\n@media screen and (min-width: 60em) {\n  .md-sidebar--secondary {\n    height: 0;\n  }\n  .no-js .md-sidebar--secondary {\n    height: auto;\n  }\n  .md-sidebar--secondary:not([hidden]) {\n    display: block;\n  }\n  .md-sidebar--secondary .md-sidebar__scrollwrap {\n    touch-action: pan-y;\n  }\n}\n.md-sidebar__scrollwrap {\n  margin: 0 0.2rem;\n  overflow-y: auto;\n  backface-visibility: hidden;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-sidebar__scrollwrap:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n\n@media screen and (max-width: 76.1875em) {\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    opacity: 0;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-overlay {\n    width: 100%;\n    height: 100%;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n  100% {\n    height: 0.65rem;\n  }\n}\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n  50% {\n    opacity: 0;\n  }\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n.md-source {\n  display: block;\n  font-size: 0.65rem;\n  line-height: 1.2;\n  white-space: nowrap;\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n}\n.md-source:focus, .md-source:hover {\n  opacity: 0.7;\n}\n.md-source__icon {\n  display: inline-block;\n  width: 2.4rem;\n  height: 2.4rem;\n  vertical-align: middle;\n}\n.md-source__icon svg {\n  margin-top: 0.6rem;\n  margin-left: 0.6rem;\n}\n[dir=rtl] .md-source__icon svg {\n  margin-right: 0.6rem;\n  margin-left: initial;\n}\n.md-source__icon + .md-source__repository {\n  margin-left: -2rem;\n  padding-left: 2rem;\n}\n[dir=rtl] .md-source__icon + .md-source__repository {\n  margin-right: -2rem;\n  margin-left: initial;\n  padding-right: 2rem;\n  padding-left: initial;\n}\n.md-source__repository {\n  display: inline-block;\n  max-width: calc(100% - 1.2rem);\n  margin-left: 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n  vertical-align: middle;\n}\n.md-source__facts {\n  margin: 0;\n  padding: 0;\n  overflow: hidden;\n  font-weight: 700;\n  font-size: 0.55rem;\n  list-style-type: none;\n  opacity: 0.75;\n}\n[data-md-state=done] .md-source__facts {\n  animation: md-source__facts--done 250ms ease-in;\n}\n.md-source__fact {\n  float: left;\n}\n[dir=rtl] .md-source__fact {\n  float: right;\n}\n[data-md-state=done] .md-source__fact {\n  animation: md-source__fact--done 400ms ease-out;\n}\n.md-source__fact::before {\n  margin: 0 0.1rem;\n  content: \"·\";\n}\n.md-source__fact:first-child::before {\n  display: none;\n}\n\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  transition: background-color 250ms;\n}\n@media print {\n  .md-tabs {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-tabs {\n    display: none;\n  }\n}\n.md-tabs[data-md-state=hidden] {\n  pointer-events: none;\n}\n.md-tabs__list {\n  margin: 0;\n  margin-left: 0.2rem;\n  padding: 0;\n  white-space: nowrap;\n  list-style: none;\n  contain: content;\n}\n[dir=rtl] .md-tabs__list {\n  margin-right: 0.2rem;\n  margin-left: initial;\n}\n.md-tabs__item {\n  display: inline-block;\n  height: 2.4rem;\n  padding-right: 0.6rem;\n  padding-left: 0.6rem;\n}\n.md-tabs__link {\n  display: block;\n  margin-top: 0.8rem;\n  font-size: 0.7rem;\n  backface-visibility: hidden;\n  opacity: 0.7;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms;\n}\n.md-tabs__link--active, .md-tabs__link:focus, .md-tabs__link:hover {\n  color: inherit;\n  opacity: 1;\n}\n.md-tabs__item:nth-child(2) .md-tabs__link {\n  transition-delay: 20ms;\n}\n.md-tabs__item:nth-child(3) .md-tabs__link {\n  transition-delay: 40ms;\n}\n.md-tabs__item:nth-child(4) .md-tabs__link {\n  transition-delay: 60ms;\n}\n.md-tabs__item:nth-child(5) .md-tabs__link {\n  transition-delay: 80ms;\n}\n.md-tabs__item:nth-child(6) .md-tabs__link {\n  transition-delay: 100ms;\n}\n.md-tabs__item:nth-child(7) .md-tabs__link {\n  transition-delay: 120ms;\n}\n.md-tabs__item:nth-child(8) .md-tabs__link {\n  transition-delay: 140ms;\n}\n.md-tabs__item:nth-child(9) .md-tabs__link {\n  transition-delay: 160ms;\n}\n.md-tabs__item:nth-child(10) .md-tabs__link {\n  transition-delay: 180ms;\n}\n.md-tabs__item:nth-child(11) .md-tabs__link {\n  transition-delay: 200ms;\n}\n.md-tabs__item:nth-child(12) .md-tabs__link {\n  transition-delay: 220ms;\n}\n.md-tabs__item:nth-child(13) .md-tabs__link {\n  transition-delay: 240ms;\n}\n.md-tabs__item:nth-child(14) .md-tabs__link {\n  transition-delay: 260ms;\n}\n.md-tabs__item:nth-child(15) .md-tabs__link {\n  transition-delay: 280ms;\n}\n.md-tabs__item:nth-child(16) .md-tabs__link {\n  transition-delay: 300ms;\n}\n.md-tabs[data-md-state=hidden] .md-tabs__link {\n  transform: translateY(50%);\n  opacity: 0;\n  transition: transform 0ms 100ms, opacity 100ms;\n}\n\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n.md-version {\n  flex-shrink: 0;\n  height: 2.4rem;\n  font-size: 0.8rem;\n}\n.md-version__current {\n  position: relative;\n  top: 0.05rem;\n  margin-right: 0.4rem;\n  margin-left: 1.4rem;\n}\n[dir=rtl] .md-version__current {\n  margin-right: 1.4rem;\n  margin-left: 0.4rem;\n}\n.md-version__current::after {\n  display: inline-block;\n  width: 0.4rem;\n  height: 0.6rem;\n  margin-left: 0.4rem;\n  background-color: currentColor;\n  mask-image: var(--md-version-icon);\n  mask-repeat: no-repeat;\n  content: \"\";\n}\n[dir=rtl] .md-version__current::after {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-version__list {\n  position: absolute;\n  top: 0.15rem;\n  z-index: 1;\n  max-height: 1.8rem;\n  margin: 0.2rem 0.8rem;\n  padding: 0;\n  overflow: auto;\n  color: var(--md-default-fg-color);\n  list-style-type: none;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  opacity: 0;\n  transition: max-height 0ms 500ms, opacity 250ms 250ms;\n  scroll-snap-type: y mandatory;\n}\n.md-version__list:focus-within, .md-version__list:hover {\n  max-height: 10rem;\n  opacity: 1;\n  transition: max-height 250ms, opacity 250ms;\n}\n.md-version__item {\n  line-height: 1.8rem;\n}\n.md-version__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  white-space: nowrap;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-version__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-version__link:focus, .md-version__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n:root {\n  --md-admonition-icon--note:\n    svg-load(\"material/pencil.svg\");\n  --md-admonition-icon--abstract:\n    svg-load(\"material/text-subject.svg\");\n  --md-admonition-icon--info:\n    svg-load(\"material/information.svg\");\n  --md-admonition-icon--tip:\n    svg-load(\"material/fire.svg\");\n  --md-admonition-icon--success:\n    svg-load(\"material/check-circle.svg\");\n  --md-admonition-icon--question:\n    svg-load(\"material/help-circle.svg\");\n  --md-admonition-icon--warning:\n    svg-load(\"material/alert.svg\");\n  --md-admonition-icon--failure:\n    svg-load(\"material/close-circle.svg\");\n  --md-admonition-icon--danger:\n    svg-load(\"material/flash-circle.svg\");\n  --md-admonition-icon--bug:\n    svg-load(\"material/bug.svg\");\n  --md-admonition-icon--example:\n    svg-load(\"material/format-list-numbered.svg\");\n  --md-admonition-icon--quote:\n    svg-load(\"material/format-quote-close.svg\");\n}\n\n.md-typeset .admonition, .md-typeset details {\n  margin: 1.5625em 0;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  color: var(--md-admonition-fg-color);\n  font-size: 0.64rem;\n  page-break-inside: avoid;\n  background-color: var(--md-admonition-bg-color);\n  border-left: 0.2rem solid #448aff;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.05);\n}\n@media print {\n  .md-typeset .admonition, .md-typeset details {\n    box-shadow: none;\n  }\n}\n[dir=rtl] .md-typeset .admonition, [dir=rtl] .md-typeset details {\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\n.md-typeset .admonition .admonition, .md-typeset details .admonition, .md-typeset .admonition details, .md-typeset details details {\n  margin: 1em 0;\n}\n.md-typeset .admonition .md-typeset__scrollwrap, .md-typeset details .md-typeset__scrollwrap {\n  margin: 1em -0.6rem;\n}\n.md-typeset .admonition .md-typeset__table, .md-typeset details .md-typeset__table {\n  padding: 0 0.6rem;\n}\n.md-typeset .admonition > .tabbed-set:only-child, .md-typeset details > .tabbed-set:only-child {\n  margin-top: 0;\n}\nhtml .md-typeset .admonition > :last-child, html .md-typeset details > :last-child {\n  margin-bottom: 0.6rem;\n}\n.md-typeset .admonition-title, .md-typeset summary {\n  position: relative;\n  margin: 0 -0.6rem 0 -0.8rem;\n  padding: 0.4rem 0.6rem 0.4rem 2rem;\n  font-weight: 700;\n  background-color: rgba(68, 138, 255, 0.1);\n  border-left: 0.2rem solid #448aff;\n}\n[dir=rtl] .md-typeset .admonition-title, [dir=rtl] .md-typeset summary {\n  margin: 0 -0.8rem 0 -0.6rem;\n  padding: 0.4rem 2rem 0.4rem 0.6rem;\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\nhtml .md-typeset .admonition-title:last-child, html .md-typeset summary:last-child {\n  margin-bottom: 0;\n}\n.md-typeset .admonition-title::before, .md-typeset summary::before {\n  position: absolute;\n  left: 0.6rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .admonition-title::before, [dir=rtl] .md-typeset summary::before {\n  right: 0.6rem;\n  left: initial;\n}\n.md-typeset .admonition-title code, .md-typeset summary code {\n  margin: initial;\n  padding: initial;\n  color: currentColor;\n  background-color: transparent;\n  border-radius: initial;\n  box-shadow: none;\n}\n.md-typeset .admonition-title + .tabbed-set:last-child, .md-typeset summary + .tabbed-set:last-child {\n  margin-top: 0;\n}\n\n.md-typeset .admonition.note, .md-typeset details.note {\n  border-color: #448aff;\n}\n\n.md-typeset .note > .admonition-title, .md-typeset .note > summary {\n  background-color: rgba(68, 138, 255, 0.1);\n  border-color: #448aff;\n}\n.md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before {\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.abstract, .md-typeset details.abstract, .md-typeset .admonition.tldr, .md-typeset details.tldr, .md-typeset .admonition.summary, .md-typeset details.summary {\n  border-color: #00b0ff;\n}\n\n.md-typeset .abstract > .admonition-title, .md-typeset .abstract > summary, .md-typeset .tldr > .admonition-title, .md-typeset .tldr > summary, .md-typeset .summary > .admonition-title, .md-typeset .summary > summary {\n  background-color: rgba(0, 176, 255, 0.1);\n  border-color: #00b0ff;\n}\n.md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before, .md-typeset .tldr > .admonition-title::before, .md-typeset .tldr > summary::before, .md-typeset .summary > .admonition-title::before, .md-typeset .summary > summary::before {\n  background-color: #00b0ff;\n  mask-image: var(--md-admonition-icon--abstract);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.info, .md-typeset details.info, .md-typeset .admonition.todo, .md-typeset details.todo {\n  border-color: #00b8d4;\n}\n\n.md-typeset .info > .admonition-title, .md-typeset .info > summary, .md-typeset .todo > .admonition-title, .md-typeset .todo > summary {\n  background-color: rgba(0, 184, 212, 0.1);\n  border-color: #00b8d4;\n}\n.md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before, .md-typeset .todo > .admonition-title::before, .md-typeset .todo > summary::before {\n  background-color: #00b8d4;\n  mask-image: var(--md-admonition-icon--info);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.tip, .md-typeset details.tip, .md-typeset .admonition.important, .md-typeset details.important, .md-typeset .admonition.hint, .md-typeset details.hint {\n  border-color: #00bfa5;\n}\n\n.md-typeset .tip > .admonition-title, .md-typeset .tip > summary, .md-typeset .important > .admonition-title, .md-typeset .important > summary, .md-typeset .hint > .admonition-title, .md-typeset .hint > summary {\n  background-color: rgba(0, 191, 165, 0.1);\n  border-color: #00bfa5;\n}\n.md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before, .md-typeset .important > .admonition-title::before, .md-typeset .important > summary::before, .md-typeset .hint > .admonition-title::before, .md-typeset .hint > summary::before {\n  background-color: #00bfa5;\n  mask-image: var(--md-admonition-icon--tip);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.success, .md-typeset details.success, .md-typeset .admonition.done, .md-typeset details.done, .md-typeset .admonition.check, .md-typeset details.check {\n  border-color: #00c853;\n}\n\n.md-typeset .success > .admonition-title, .md-typeset .success > summary, .md-typeset .done > .admonition-title, .md-typeset .done > summary, .md-typeset .check > .admonition-title, .md-typeset .check > summary {\n  background-color: rgba(0, 200, 83, 0.1);\n  border-color: #00c853;\n}\n.md-typeset .success > .admonition-title::before, .md-typeset .success > summary::before, .md-typeset .done > .admonition-title::before, .md-typeset .done > summary::before, .md-typeset .check > .admonition-title::before, .md-typeset .check > summary::before {\n  background-color: #00c853;\n  mask-image: var(--md-admonition-icon--success);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.question, .md-typeset details.question, .md-typeset .admonition.faq, .md-typeset details.faq, .md-typeset .admonition.help, .md-typeset details.help {\n  border-color: #64dd17;\n}\n\n.md-typeset .question > .admonition-title, .md-typeset .question > summary, .md-typeset .faq > .admonition-title, .md-typeset .faq > summary, .md-typeset .help > .admonition-title, .md-typeset .help > summary {\n  background-color: rgba(100, 221, 23, 0.1);\n  border-color: #64dd17;\n}\n.md-typeset .question > .admonition-title::before, .md-typeset .question > summary::before, .md-typeset .faq > .admonition-title::before, .md-typeset .faq > summary::before, .md-typeset .help > .admonition-title::before, .md-typeset .help > summary::before {\n  background-color: #64dd17;\n  mask-image: var(--md-admonition-icon--question);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.warning, .md-typeset details.warning, .md-typeset .admonition.attention, .md-typeset details.attention, .md-typeset .admonition.caution, .md-typeset details.caution {\n  border-color: #ff9100;\n}\n\n.md-typeset .warning > .admonition-title, .md-typeset .warning > summary, .md-typeset .attention > .admonition-title, .md-typeset .attention > summary, .md-typeset .caution > .admonition-title, .md-typeset .caution > summary {\n  background-color: rgba(255, 145, 0, 0.1);\n  border-color: #ff9100;\n}\n.md-typeset .warning > .admonition-title::before, .md-typeset .warning > summary::before, .md-typeset .attention > .admonition-title::before, .md-typeset .attention > summary::before, .md-typeset .caution > .admonition-title::before, .md-typeset .caution > summary::before {\n  background-color: #ff9100;\n  mask-image: var(--md-admonition-icon--warning);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.failure, .md-typeset details.failure, .md-typeset .admonition.missing, .md-typeset details.missing, .md-typeset .admonition.fail, .md-typeset details.fail {\n  border-color: #ff5252;\n}\n\n.md-typeset .failure > .admonition-title, .md-typeset .failure > summary, .md-typeset .missing > .admonition-title, .md-typeset .missing > summary, .md-typeset .fail > .admonition-title, .md-typeset .fail > summary {\n  background-color: rgba(255, 82, 82, 0.1);\n  border-color: #ff5252;\n}\n.md-typeset .failure > .admonition-title::before, .md-typeset .failure > summary::before, .md-typeset .missing > .admonition-title::before, .md-typeset .missing > summary::before, .md-typeset .fail > .admonition-title::before, .md-typeset .fail > summary::before {\n  background-color: #ff5252;\n  mask-image: var(--md-admonition-icon--failure);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.danger, .md-typeset details.danger, .md-typeset .admonition.error, .md-typeset details.error {\n  border-color: #ff1744;\n}\n\n.md-typeset .danger > .admonition-title, .md-typeset .danger > summary, .md-typeset .error > .admonition-title, .md-typeset .error > summary {\n  background-color: rgba(255, 23, 68, 0.1);\n  border-color: #ff1744;\n}\n.md-typeset .danger > .admonition-title::before, .md-typeset .danger > summary::before, .md-typeset .error > .admonition-title::before, .md-typeset .error > summary::before {\n  background-color: #ff1744;\n  mask-image: var(--md-admonition-icon--danger);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.bug, .md-typeset details.bug {\n  border-color: #f50057;\n}\n\n.md-typeset .bug > .admonition-title, .md-typeset .bug > summary {\n  background-color: rgba(245, 0, 87, 0.1);\n  border-color: #f50057;\n}\n.md-typeset .bug > .admonition-title::before, .md-typeset .bug > summary::before {\n  background-color: #f50057;\n  mask-image: var(--md-admonition-icon--bug);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.example, .md-typeset details.example {\n  border-color: #7c4dff;\n}\n\n.md-typeset .example > .admonition-title, .md-typeset .example > summary {\n  background-color: rgba(124, 77, 255, 0.1);\n  border-color: #7c4dff;\n}\n.md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before {\n  background-color: #7c4dff;\n  mask-image: var(--md-admonition-icon--example);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.quote, .md-typeset details.quote, .md-typeset .admonition.cite, .md-typeset details.cite {\n  border-color: #9e9e9e;\n}\n\n.md-typeset .quote > .admonition-title, .md-typeset .quote > summary, .md-typeset .cite > .admonition-title, .md-typeset .cite > summary {\n  background-color: rgba(158, 158, 158, 0.1);\n  border-color: #9e9e9e;\n}\n.md-typeset .quote > .admonition-title::before, .md-typeset .quote > summary::before, .md-typeset .cite > .admonition-title::before, .md-typeset .cite > summary::before {\n  background-color: #9e9e9e;\n  mask-image: var(--md-admonition-icon--quote);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n.md-typeset [id^=\"fnref:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n}\n.md-typeset [id^=\"fn:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n}\n.md-typeset .footnote {\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n}\n.md-typeset .footnote ol {\n  margin-left: 0;\n}\n.md-typeset .footnote li {\n  transition: color 125ms;\n}\n.md-typeset .footnote li:target {\n  color: var(--md-default-fg-color);\n}\n.md-typeset .footnote li:hover .footnote-backref, .md-typeset .footnote li:target .footnote-backref {\n  transform: translateX(0);\n  opacity: 1;\n}\n.md-typeset .footnote li > :first-child {\n  margin-top: 0;\n}\n.md-typeset .footnote-backref {\n  display: inline-block;\n  color: var(--md-typeset-a-color);\n  font-size: 0;\n  vertical-align: text-bottom;\n  transform: translateX(0.25rem);\n  opacity: 0;\n  transition: color 250ms, transform 250ms 250ms, opacity 125ms 250ms;\n}\n@media print {\n  .md-typeset .footnote-backref {\n    color: var(--md-typeset-a-color);\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n[dir=rtl] .md-typeset .footnote-backref {\n  transform: translateX(-0.25rem);\n}\n.md-typeset .footnote-backref:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset .footnote-backref::before {\n  display: inline-block;\n  width: 0.8rem;\n  height: 0.8rem;\n  background-color: currentColor;\n  mask-image: var(--md-footnotes-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .footnote-backref::before svg {\n  transform: scaleX(-1);\n}\n\n.md-typeset .headerlink {\n  display: inline-block;\n  margin-left: 0.5rem;\n  color: var(--md-default-fg-color--lighter);\n  opacity: 0;\n  transition: color 250ms, opacity 125ms;\n}\n@media print {\n  .md-typeset .headerlink {\n    display: none;\n  }\n}\n[dir=rtl] .md-typeset .headerlink {\n  margin-right: 0.5rem;\n  margin-left: initial;\n}\n.md-typeset :hover > .headerlink,\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus {\n  opacity: 1;\n  transition: color 250ms, opacity 125ms;\n}\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus,\n.md-typeset .headerlink:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset :target {\n  scroll-margin-top: 3.6rem;\n}\n.md-typeset h1:target,\n.md-typeset h2:target,\n.md-typeset h3:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h1:target::before,\n.md-typeset h2:target::before,\n.md-typeset h3:target::before {\n  display: block;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n  content: \"\";\n}\n.md-typeset h4:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h4:target::before {\n  display: block;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n  content: \"\";\n}\n.md-typeset h5:target,\n.md-typeset h6:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h5:target::before,\n.md-typeset h6:target::before {\n  display: block;\n  margin-top: -3.6rem;\n  padding-top: 3.6rem;\n  content: \"\";\n}\n\n.md-typeset div.arithmatex {\n  overflow: auto;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset div.arithmatex {\n    margin: 0 -0.8rem;\n  }\n}\n.md-typeset div.arithmatex > * {\n  width: min-content;\n  margin: 1em auto !important;\n  padding: 0 0.8rem;\n  touch-action: auto;\n}\n\n.md-typeset del.critic,\n.md-typeset ins.critic,\n.md-typeset .critic.comment {\n  box-decoration-break: clone;\n}\n.md-typeset del.critic {\n  background-color: var(--md-typeset-del-color);\n}\n.md-typeset ins.critic {\n  background-color: var(--md-typeset-ins-color);\n}\n.md-typeset .critic.comment {\n  color: var(--md-code-hl-comment-color);\n}\n.md-typeset .critic.comment::before {\n  content: \"/* \";\n}\n.md-typeset .critic.comment::after {\n  content: \" */\";\n}\n.md-typeset .critic.block {\n  display: block;\n  margin: 1em 0;\n  padding-right: 0.8rem;\n  padding-left: 0.8rem;\n  overflow: auto;\n  box-shadow: none;\n}\n.md-typeset .critic.block > :first-child {\n  margin-top: 0.5em;\n}\n.md-typeset .critic.block > :last-child {\n  margin-bottom: 0.5em;\n}\n\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n.md-typeset details {\n  display: flow-root;\n  padding-top: 0;\n  overflow: visible;\n}\n.md-typeset details[open] > summary::after {\n  transform: rotate(90deg);\n}\n.md-typeset details:not([open]) {\n  padding-bottom: 0;\n  box-shadow: none;\n}\n.md-typeset details:not([open]) > summary {\n  border-radius: 0.1rem;\n}\n.md-typeset details::after {\n  display: table;\n  content: \"\";\n}\n.md-typeset summary {\n  display: block;\n  min-height: 1rem;\n  padding: 0.4rem 1.8rem 0.4rem 2rem;\n  border-top-left-radius: 0.1rem;\n  border-top-right-radius: 0.1rem;\n  cursor: pointer;\n}\n[dir=rtl] .md-typeset summary {\n  padding: 0.4rem 2.2rem 0.4rem 1.8rem;\n}\n.md-typeset summary:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset summary::after {\n  position: absolute;\n  top: 0.4rem;\n  right: 0.4rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: currentColor;\n  mask-image: var(--md-details-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  transform: rotate(0deg);\n  transition: transform 250ms;\n  content: \"\";\n}\n[dir=rtl] .md-typeset summary::after {\n  right: initial;\n  left: 0.4rem;\n  transform: rotate(180deg);\n}\n.md-typeset summary::marker, .md-typeset summary::-webkit-details-marker {\n  display: none;\n}\n\n.md-typeset .emojione,\n.md-typeset .twemoji,\n.md-typeset .gemoji {\n  display: inline-flex;\n  height: 1.125em;\n  vertical-align: text-top;\n}\n.md-typeset .emojione svg,\n.md-typeset .twemoji svg,\n.md-typeset .gemoji svg {\n  width: 1.125em;\n  max-height: 100%;\n  fill: currentColor;\n}\n\n.highlight .o,\n.highlight .ow {\n  color: var(--md-code-hl-operator-color);\n}\n.highlight .p {\n  color: var(--md-code-hl-punctuation-color);\n}\n.highlight .cpf,\n.highlight .l,\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .s2,\n.highlight .si,\n.highlight .s1,\n.highlight .ss {\n  color: var(--md-code-hl-string-color);\n}\n.highlight .cp,\n.highlight .se,\n.highlight .sh,\n.highlight .sr,\n.highlight .sx {\n  color: var(--md-code-hl-special-color);\n}\n.highlight .m,\n.highlight .mb,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo {\n  color: var(--md-code-hl-number-color);\n}\n.highlight .k,\n.highlight .kd,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .kc,\n.highlight .n {\n  color: var(--md-code-hl-name-color);\n}\n.highlight .no,\n.highlight .nb,\n.highlight .bp {\n  color: var(--md-code-hl-constant-color);\n}\n.highlight .nc,\n.highlight .ne,\n.highlight .nf,\n.highlight .nn {\n  color: var(--md-code-hl-function-color);\n}\n.highlight .nd,\n.highlight .ni,\n.highlight .nl,\n.highlight .nt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .c,\n.highlight .cm,\n.highlight .c1,\n.highlight .ch,\n.highlight .cs,\n.highlight .sd {\n  color: var(--md-code-hl-comment-color);\n}\n.highlight .na,\n.highlight .nv,\n.highlight .vc,\n.highlight .vg,\n.highlight .vi {\n  color: var(--md-code-hl-variable-color);\n}\n.highlight .ge,\n.highlight .gr,\n.highlight .gh,\n.highlight .go,\n.highlight .gp,\n.highlight .gs,\n.highlight .gu,\n.highlight .gt {\n  color: var(--md-code-hl-generic-color);\n}\n.highlight .gd,\n.highlight .gi {\n  margin: 0 -0.125em;\n  padding: 0 0.125em;\n  border-radius: 0.1rem;\n}\n.highlight .gd {\n  background-color: var(--md-typeset-del-color);\n}\n.highlight .gi {\n  background-color: var(--md-typeset-ins-color);\n}\n.highlight .hll {\n  display: block;\n  margin: 0 -1.1764705882em;\n  padding: 0 1.1764705882em;\n  background-color: var(--md-code-hl-color);\n}\n.highlight [data-linenos]::before {\n  position: sticky;\n  left: -1.1764705882em;\n  float: left;\n  margin-right: 1.1764705882em;\n  margin-left: -1.1764705882em;\n  padding-left: 1.1764705882em;\n  color: var(--md-default-fg-color--light);\n  background-color: var(--md-code-bg-color);\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n  content: attr(data-linenos);\n  user-select: none;\n}\n\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n}\n.highlighttable tbody,\n.highlighttable td {\n  display: block;\n  padding: 0;\n}\n.highlighttable tr {\n  display: flex;\n}\n.highlighttable pre {\n  margin: 0;\n}\n.highlighttable .linenos {\n  padding: 0.7720588235em 1.1764705882em;\n  padding-right: 0;\n  font-size: 0.85em;\n  background-color: var(--md-code-bg-color);\n  user-select: none;\n}\n.highlighttable .linenodiv {\n  padding-right: 0.5882352941em;\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n}\n.highlighttable .linenodiv pre {\n  color: var(--md-default-fg-color--light);\n  text-align: right;\n}\n.highlighttable .code {\n  flex: 1;\n  overflow: hidden;\n}\n\n.md-typeset .highlighttable {\n  margin: 1em 0;\n  direction: ltr;\n  border-radius: 0.1rem;\n}\n.md-typeset .highlighttable code {\n  border-radius: 0;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > .highlight {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > .highlight .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n  .md-typeset > .highlight code {\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable {\n    margin: 1em -0.8rem;\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n}\n\n.md-typeset .keys kbd::before,\n.md-typeset .keys kbd::after {\n  position: relative;\n  margin: 0;\n  color: inherit;\n  -moz-osx-font-smoothing: initial;\n  -webkit-font-smoothing: initial;\n}\n.md-typeset .keys span {\n  padding: 0 0.2em;\n  color: var(--md-default-fg-color--light);\n}\n.md-typeset .keys .key-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-left-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-right-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-left-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-right-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-left-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-right-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-left-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-right-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-left-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-right-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-left-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-right-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-left-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-right-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-left-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-right-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-arrow-down::before {\n  padding-right: 0.4em;\n  content: \"↓\";\n}\n.md-typeset .keys .key-arrow-left::before {\n  padding-right: 0.4em;\n  content: \"←\";\n}\n.md-typeset .keys .key-arrow-right::before {\n  padding-right: 0.4em;\n  content: \"→\";\n}\n.md-typeset .keys .key-arrow-up::before {\n  padding-right: 0.4em;\n  content: \"↑\";\n}\n.md-typeset .keys .key-backspace::before {\n  padding-right: 0.4em;\n  content: \"⌫\";\n}\n.md-typeset .keys .key-backtab::before {\n  padding-right: 0.4em;\n  content: \"⇤\";\n}\n.md-typeset .keys .key-caps-lock::before {\n  padding-right: 0.4em;\n  content: \"⇪\";\n}\n.md-typeset .keys .key-clear::before {\n  padding-right: 0.4em;\n  content: \"⌧\";\n}\n.md-typeset .keys .key-context-menu::before {\n  padding-right: 0.4em;\n  content: \"☰\";\n}\n.md-typeset .keys .key-delete::before {\n  padding-right: 0.4em;\n  content: \"⌦\";\n}\n.md-typeset .keys .key-eject::before {\n  padding-right: 0.4em;\n  content: \"⏏\";\n}\n.md-typeset .keys .key-end::before {\n  padding-right: 0.4em;\n  content: \"⤓\";\n}\n.md-typeset .keys .key-escape::before {\n  padding-right: 0.4em;\n  content: \"⎋\";\n}\n.md-typeset .keys .key-home::before {\n  padding-right: 0.4em;\n  content: \"⤒\";\n}\n.md-typeset .keys .key-insert::before {\n  padding-right: 0.4em;\n  content: \"⎀\";\n}\n.md-typeset .keys .key-page-down::before {\n  padding-right: 0.4em;\n  content: \"⇟\";\n}\n.md-typeset .keys .key-page-up::before {\n  padding-right: 0.4em;\n  content: \"⇞\";\n}\n.md-typeset .keys .key-print-screen::before {\n  padding-right: 0.4em;\n  content: \"⎙\";\n}\n.md-typeset .keys .key-tab::after {\n  padding-left: 0.4em;\n  content: \"⇥\";\n}\n.md-typeset .keys .key-num-enter::after {\n  padding-left: 0.4em;\n  content: \"⌤\";\n}\n.md-typeset .keys .key-enter::after {\n  padding-left: 0.4em;\n  content: \"⏎\";\n}\n\n.md-typeset .tabbed-content {\n  display: none;\n  order: 99;\n  width: 100%;\n  box-shadow: 0 -0.05rem var(--md-default-fg-color--lightest);\n}\n@media print {\n  .md-typeset .tabbed-content {\n    display: block;\n    order: initial;\n  }\n}\n.md-typeset .tabbed-content > pre:only-child,\n.md-typeset .tabbed-content > .highlight:only-child pre,\n.md-typeset .tabbed-content > .highlighttable:only-child {\n  margin: 0;\n}\n.md-typeset .tabbed-content > pre:only-child > code,\n.md-typeset .tabbed-content > .highlight:only-child pre > code,\n.md-typeset .tabbed-content > .highlighttable:only-child > code {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.md-typeset .tabbed-content > .tabbed-set {\n  margin: 0;\n}\n.md-typeset .tabbed-set {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 1em 0;\n  border-radius: 0.1rem;\n}\n.md-typeset .tabbed-set > input {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-typeset .tabbed-set > input:checked + label {\n  color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .tabbed-set > input:checked + label + .tabbed-content {\n  display: block;\n}\n.md-typeset .tabbed-set > input:focus + label {\n  outline-style: auto;\n}\n.md-typeset .tabbed-set > input:not(.focus-visible) + label {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset .tabbed-set > label {\n  z-index: 1;\n  width: auto;\n  padding: 0.9375em 1.25em 0.78125em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.64rem;\n  border-bottom: 0.1rem solid transparent;\n  cursor: pointer;\n  transition: color 250ms;\n}\n.md-typeset .tabbed-set > label:hover {\n  color: var(--md-accent-fg-color);\n}\n\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n.md-typeset .task-list-item {\n  position: relative;\n  list-style-type: none;\n}\n.md-typeset .task-list-item [type=checkbox] {\n  position: absolute;\n  top: 0.45em;\n  left: -2em;\n}\n[dir=rtl] .md-typeset .task-list-item [type=checkbox] {\n  right: -2em;\n  left: initial;\n}\n.md-typeset .task-list-control [type=checkbox] {\n  z-index: -1;\n  opacity: 0;\n}\n.md-typeset .task-list-indicator::before {\n  position: absolute;\n  top: 0.15em;\n  left: -1.5em;\n  width: 1.25em;\n  height: 1.25em;\n  background-color: var(--md-default-fg-color--lightest);\n  mask-image: var(--md-tasklist-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .task-list-indicator::before {\n  right: -1.5em;\n  left: initial;\n}\n.md-typeset [type=checkbox]:checked + .task-list-indicator::before {\n  background-color: #00e676;\n  mask-image: var(--md-tasklist-icon--checked);\n}\n\n@media screen and (min-width: 45em) {\n  .md-typeset .inline {\n    float: left;\n    width: 11.7rem;\n    margin-top: 0;\n    margin-right: 0.8rem;\n    margin-bottom: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  .md-typeset .inline.end {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline.end {\n    float: left;\n    margin-right: 0.8rem;\n    margin-left: 0;\n  }\n}\n\n/*# sourceMappingURL=main.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Enforce correct box model and prevent adjustments of font size after\n// orientation changes in IE and iOS\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n// All elements shall inherit the document default\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\n// Remove margin in all browsers\nbody {\n  margin: 0;\n}\n\n// Reset tap outlines on iOS and Android\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\n// Reset link styles\na {\n  color: inherit;\n  text-decoration: none;\n}\n\n// Normalize horizontal separator styles\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: px2rem(1px);\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\n// Normalize font-size in all browsers\nsmall {\n  font-size: 80%;\n}\n\n// Prevent subscript and superscript from affecting line-height\nsub,\nsup {\n  line-height: 1em;\n}\n\n// Remove border on image\nimg {\n  border-style: none;\n}\n\n// Reset table styles\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\n// Reset table cell styles\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\n// Reset button styles\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\n// Reset input styles\ninput {\n  border: 0;\n  outline: none;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Color definitions\n:root {\n\n  // Default color shades\n  --md-default-fg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter:      hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest:     hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter:      hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest:     hsla(0, 0%, 100%, 0.12);\n\n  // Primary color shades\n  --md-primary-fg-color:               hsla(#{hex2hsl($clr-indigo-500)}, 1);\n  --md-primary-fg-color--light:        hsla(#{hex2hsl($clr-indigo-400)}, 1);\n  --md-primary-fg-color--dark:         hsla(#{hex2hsl($clr-indigo-700)}, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Accent color shades\n  --md-accent-fg-color:                hsla(#{hex2hsl($clr-indigo-a200)}, 1);\n  --md-accent-fg-color--transparent:   hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);\n  --md-accent-bg-color:                hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light:         hsla(0, 0%, 100%, 0.7);\n\n  // Light theme (default)\n  > * {\n\n    // Code color shades\n    --md-code-fg-color:                hsla(200, 18%, 26%, 1);\n    --md-code-bg-color:                hsla(0, 0%, 96%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n    --md-code-hl-number-color:         hsla(0, 67%, 50%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 47%, 1);\n    --md-code-hl-function-color:       hsla(291, 45%, 50%, 1);\n    --md-code-hl-constant-color:       hsla(250, 63%, 60%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 54%, 51%, 1);\n    --md-code-hl-string-color:         hsla(150, 63%, 30%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n\n    // Typeset `del` and `ins` color shades\n    --md-typeset-del-color:            hsla(6, 90%, 60%, 0.15);\n    --md-typeset-ins-color:            hsla(150, 90%, 44%, 0.15);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(0, 0%, 98%, 1);\n    --md-typeset-kbd-accent-color:     hsla(0, 100%, 100%, 1);\n    --md-typeset-kbd-border-color:     hsla(0, 0%, 72%, 1);\n\n    // Admonition color shades\n    --md-admonition-fg-color:          var(--md-default-fg-color);\n    --md-admonition-bg-color:          var(--md-default-bg-color);\n\n    // Footer color shades\n    --md-footer-fg-color:              hsla(0, 0%, 100%, 1);\n    --md-footer-fg-color--light:       hsla(0, 0%, 100%, 0.7);\n    --md-footer-fg-color--lighter:     hsla(0, 0%, 100%, 0.3);\n    --md-footer-bg-color:              hsla(0, 0%, 0%, 0.87);\n    --md-footer-bg-color--dark:        hsla(0, 0%, 0%, 0.32);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon\n.md-icon {\n\n  // SVG defaults\n  svg {\n    display: block;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    fill: currentColor;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: font definitions\n// ----------------------------------------------------------------------------\n\n// Enable font-smoothing in Webkit and FF\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// Define default fonts\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family:\n    var(--md-text-font-family, _),\n    -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\n// Define monospaced fonts\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family:\n    var(--md-code-font-family, _),\n    SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: typesetted content\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Content that is typeset - if possible, all margins, paddings and font sizes\n// should be set in ems, so nested blocks (e.g. admonitions) render correctly.\n.md-typeset {\n  font-size: px2rem(16px);\n  line-height: 1.6;\n  color-adjust: exact;\n\n  // [print]: We'll use a smaller `font-size` for printing, so code examples\n  // don't break too early, and `16px` looks too big anyway.\n  @media print {\n    font-size: px2rem(13.6px);\n  }\n\n  // Default spacing\n  ul,\n  ol,\n  dl,\n  figure,\n  blockquote,\n  pre {\n    display: flow-root;\n    margin: 1em 0;\n  }\n\n  // Headline on level 1\n  h1 {\n    margin: 0 0 px2em(40px, 32px);\n    color: var(--md-default-fg-color--light);\n    font-weight: 300;\n    font-size: px2em(32px);\n    line-height: 1.3;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 2\n  h2 {\n    margin: px2em(40px, 25px) 0 px2em(16px, 25px);\n    font-weight: 300;\n    font-size: px2em(25px);\n    line-height: 1.4;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3\n  h3 {\n    margin: px2em(32px, 20px) 0 px2em(16px, 20px);\n    font-weight: 400;\n    font-size: px2em(20px);\n    line-height: 1.5;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3 following level 2\n  h2 + h3 {\n    margin-top: px2em(16px, 20px);\n  }\n\n  // Headline on level 4\n  h4 {\n    margin: px2em(16px) 0;\n    font-weight: 700;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5-6\n  h5,\n  h6 {\n    margin: px2em(16px, 12.8px) 0;\n    color: var(--md-default-fg-color--light);\n    font-weight: 700;\n    font-size: px2em(12.8px);\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5\n  h5 {\n    text-transform: uppercase;\n  }\n\n  // Horizontal separator\n  hr {\n    display: flow-root;\n    margin: 1.5em 0;\n    border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);\n  }\n\n  // Text link\n  a {\n    color: var(--md-typeset-a-color);\n    word-break: break-word;\n\n    // Also enable color transition on pseudo elements\n    &,\n    &::before {\n      transition: color 125ms;\n    }\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Code block\n  code,\n  pre,\n  kbd {\n    color: var(--md-code-fg-color);\n    direction: ltr;\n\n    // [print]: Wrap text and hide scollbars\n    @media print {\n      white-space: pre-wrap;\n    }\n  }\n\n  // Inline code block\n  code {\n    padding: 0 px2em(4px, 13.6px);\n    font-size: px2em(13.6px);\n    word-break: break-word;\n    background-color: var(--md-code-bg-color);\n    border-radius: px2rem(2px);\n    box-decoration-break: clone;\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n  }\n\n  // Code block in headline\n  h1 code,\n  h2 code,\n  h3 code,\n  h4 code,\n  h5 code,\n  h6 code {\n    margin: initial;\n    padding: initial;\n    background-color: transparent;\n    box-shadow: none;\n  }\n\n  // Ensure link color in code blocks\n  a > code {\n    color: currentColor;\n  }\n\n  // Unformatted content\n  pre {\n    position: relative;\n    line-height: 1.4;\n\n    // Code block\n    > code {\n      display: block;\n      margin: 0;\n      padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n      overflow: auto;\n      word-break: normal;\n      box-shadow: none;\n      box-decoration-break: slice;\n      touch-action: auto;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Code block on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Unformatted text\n    > pre {\n      margin: 1em px2rem(-16px);\n\n      // Code block\n      code {\n        border-radius: 0;\n      }\n    }\n  }\n\n  // Keyboard key\n  kbd {\n    display: inline-block;\n    padding: 0 px2em(8px, 12px);\n    color: var(--md-default-fg-color);\n    font-size: px2em(12px);\n    vertical-align: text-top;\n    word-break: break-word;\n    background-color: var(--md-typeset-kbd-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(2px)  0 px2rem(1px) var(--md-typeset-kbd-border-color),\n      0 px2rem(2px)  0             var(--md-typeset-kbd-border-color),\n      0 px2rem(-2px) px2rem(4px)   var(--md-typeset-kbd-accent-color) inset;\n  }\n\n  // Text highlighting marker\n  mark {\n    color: inherit;\n    word-break: break-word;\n    background-color: var(--md-typeset-mark-color);\n    box-decoration-break: clone;\n  }\n\n  // Abbreviation\n  abbr {\n    text-decoration: none;\n    border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);\n    cursor: help;\n\n    // Show tooltip for touch devices\n    @media (hover: none) {\n      position: relative;\n\n      // Tooltip\n      &[title]:focus::after,\n      &[title]:hover::after {\n        @include z-depth(2);\n\n        position: absolute;\n        left: 0;\n        display: inline-block;\n        width: auto;\n        min-width: max-content;\n        max-width: 80%;\n        margin-top: 2em;\n        padding: px2rem(4px) px2rem(6px);\n        color: var(--md-default-bg-color);\n        font-size: px2rem(14px);\n        background-color: var(--md-default-fg-color);\n        border-radius: px2rem(2px);\n        content: attr(title);\n      }\n    }\n  }\n\n  // Small text\n  small {\n    opacity: 0.75;\n  }\n\n  // Superscript and subscript\n  sup,\n  sub {\n    margin-left: px2em(1px, 12.8px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(1px, 12.8px);\n      margin-left: initial;\n    }\n  }\n\n  // Blockquotes, possibly nested\n  blockquote {\n    padding-left: px2rem(12px);\n    color: var(--md-default-fg-color--light);\n    border-left: px2rem(4px) solid var(--md-default-fg-color--lighter);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: initial;\n      border-right: px2rem(4px) solid var(--md-default-fg-color--lighter);\n      border-left: initial;\n    }\n  }\n\n  // Unordered list\n  ul {\n    list-style-type: disc;\n  }\n\n  // Unordered and ordered list\n  ul,\n  ol {\n    margin-left: px2em(10px);\n    padding: 0;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(10px);\n      margin-left: initial;\n    }\n\n    // Nested ordered list\n    ol {\n      list-style-type: lower-alpha;\n\n      // Triply nested ordered list\n      ol {\n        list-style-type: lower-roman;\n      }\n    }\n\n    // List element\n    li {\n      margin-bottom: 0.5em;\n      margin-left: px2em(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2em(20px);\n        margin-left: initial;\n      }\n\n      // Adjust spacing\n      p,\n      blockquote {\n        margin: 0.5em 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Nested list\n      ul,\n      ol {\n        margin: 0.5em 0 0.5em px2em(10px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2em(10px);\n          margin-left: initial;\n        }\n      }\n    }\n  }\n\n  // Definition list\n  dd {\n    margin: 1em 0 1.5em px2em(30px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(30px);\n      margin-left: initial;\n    }\n  }\n\n  // Image or icon\n  img,\n  svg {\n    max-width: 100%;\n    height: auto;\n\n    // Adjust spacing when left-aligned\n    &[align=\"left\"] {\n      margin: 1em;\n      margin-left: 0;\n    }\n\n    // Adjust spacing when right-aligned\n    &[align=\"right\"] {\n      margin: 1em;\n      margin-right: 0;\n    }\n\n    // Adjust spacing when sole children\n    &[align]:only-child {\n      margin-top: 0;\n    }\n  }\n\n  // Figure\n  figure {\n    width: fit-content;\n    max-width: 100%;\n    margin: 0 auto;\n    text-align: center;\n\n    // Figure images\n    img {\n      display: block;\n    }\n  }\n\n  // Figure caption\n  figcaption {\n    max-width: px2rem(480px);\n    margin: 1em auto 2em;\n    font-style: italic;\n  }\n\n  // Limit width to container\n  iframe {\n    max-width: 100%;\n  }\n\n  // Data table\n  table:not([class]) {\n    display: inline-block;\n    max-width: 100%;\n    overflow: auto;\n    font-size: px2rem(12.8px);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    touch-action: auto;\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: table;\n    }\n\n    // Due to margin collapse because of the necessary inline-block hack, we\n    // cannot increase the bottom margin on the table, so we just increase the\n    // top margin on the following element\n    + * {\n      margin-top: 1.5em;\n    }\n\n    // Elements in table heading and cell\n    th > *,\n    td > * {\n\n      // Adjust spacing on first child\n      &:first-child {\n        margin-top: 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    // Table heading and cell\n    th:not([align]),\n    td:not([align]) {\n      text-align: left;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        text-align: right;\n      }\n    }\n\n    // Table heading\n    th {\n      min-width: px2rem(100px);\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      color: var(--md-default-bg-color);\n      vertical-align: top;\n      background-color: var(--md-default-fg-color--light);\n\n      // Links in table headings\n      a {\n        color: inherit;\n      }\n    }\n\n    // Table cell\n    td {\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      vertical-align: top;\n      border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n    }\n\n    // Table row\n    tr {\n      transition: background-color 125ms;\n\n      // Table row on hover\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.035);\n        box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;\n      }\n\n      // Hide border on first table row\n      &:first-child td {\n        border-top: 0;\n      }\n    }\n\n    // Text link in table\n    a {\n      word-break: normal;\n    }\n  }\n\n  // Sortable table\n  table th[role=\"columnheader\"] {\n    cursor: pointer;\n\n    // Sort icon\n    &::after {\n      display: inline-block;\n      width: 1.2em;\n      height: 1.2em;\n      margin-left: 0.5em;\n      vertical-align: sub;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Sort ascending\n    &[aria-sort=\"ascending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--ascending);\n    }\n\n    // Sort descending\n    &[aria-sort=\"descending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--descending);\n    }\n  }\n\n  // Data table scroll wrapper\n  &__scrollwrap {\n    margin: 1em px2rem(-16px);\n    overflow-x: auto;\n    touch-action: auto;\n  }\n\n  // Data table wrapper\n  &__table {\n    display: inline-block;\n    margin-bottom: 0.5em;\n    padding: 0 px2rem(16px);\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: block;\n    }\n\n    // Data table\n    html & table {\n      display: table;\n      width: 100%;\n      margin: 0;\n      overflow: hidden;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","//\n// Name:           Material Shadows\n// Description:    Mixins for Material Design Shadows.\n// Version:        3.0.1\n//\n// Author:         Denis Malinochkin\n// Git:            https://github.com/mrmlnc/material-shadows\n//\n// twitter:        @mrmlnc\n//\n// ------------------------------------\n\n\n// Mixins\n// ------------------------------------\n\n@mixin z-depth-transition() {\n  transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1);\n}\n\n@mixin z-depth-focus() {\n  box-shadow: 0 0 8px rgba(0, 0, 0, .18), 0 8px 16px rgba(0, 0, 0, .36);\n}\n\n@mixin z-depth-2dp() {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14),\n              0 1px 5px 0 rgba(0, 0, 0, .12),\n              0 3px 1px -2px rgba(0, 0, 0, .2);\n}\n\n@mixin z-depth-3dp() {\n  box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .14),\n              0 1px 8px 0 rgba(0, 0, 0, .12),\n              0 3px 3px -2px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-4dp() {\n  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14),\n              0 1px 10px 0 rgba(0, 0, 0, .12),\n              0 2px 4px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-6dp() {\n  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14),\n              0 1px 18px 0 rgba(0, 0, 0, .12),\n              0 3px 5px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-8dp() {\n  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14),\n              0 3px 14px 2px rgba(0, 0, 0, .12),\n              0 5px 5px -3px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-16dp() {\n  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14),\n              0  6px 30px 5px rgba(0, 0, 0, .12),\n              0  8px 10px -5px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-24dp() {\n  box-shadow: 0  9px 46px  8px rgba(0, 0, 0, .14),\n              0 24px 38px  3px rgba(0, 0, 0, .12),\n              0 11px 15px -7px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth($dp: 2) {\n  @if $dp == 2 {\n    @include z-depth-2dp();\n  } @else if $dp == 3 {\n    @include z-depth-3dp();\n  } @else if $dp == 4 {\n    @include z-depth-4dp();\n  } @else if $dp == 6 {\n    @include z-depth-6dp();\n  } @else if $dp == 8 {\n    @include z-depth-8dp();\n  } @else if $dp == 16 {\n    @include z-depth-16dp();\n  } @else if $dp == 24 {\n    @include z-depth-24dp();\n  }\n}\n\n\n// Class generator\n// ------------------------------------\n\n@mixin z-depth-classes($transition: false, $focus: false) {\n  @if $transition == true {\n    &-transition {\n      @include z-depth-transition();\n    }\n  }\n\n  @if $focus == true {\n    &-focus {\n      @include z-depth-focus();\n    }\n  }\n\n  // The available values for the shadow depth\n  @each $depth in 2, 3, 4, 6, 8, 16, 24 {\n    &-#{$depth}dp {\n      @include z-depth($depth);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: base grid and containers\n// ----------------------------------------------------------------------------\n\n// Stretch container to viewport and set base `font-size`\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  // Hack: normally, we would set the base `font-size` to `62.5%`, so we can\n  // base all calculations on `10px`, but Chromium and Chrome define a minimal\n  // `font-size` of `12px` if the system language is set to Chinese. For this\n  // reason we just double the `font-size` and set it to `20px`.\n  //\n  // See https://github.com/squidfunk/mkdocs-material/issues/911\n  font-size: 125%;\n\n  // [screen medium +]: Set base `font-size` to `11px`\n  @include break-from-device(screen medium) {\n    font-size: 137.5%;\n  }\n\n  // [screen large +]: Set base `font-size` to `12px`\n  @include break-from-device(screen large) {\n    font-size: 150%;\n  }\n}\n\n// Stretch body to container - flexbox is used, so the footer will always be\n// aligned to the bottom of the viewport\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  // Hack: reset `font-size` to `10px`, so the spacing for all inline elements\n  // is correct again. Otherwise the spacing would be based on `20px`.\n  font-size: px2rem(10px);\n  background-color: var(--md-default-bg-color);\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n\n  // Body in locked state\n  &[data-md-state=\"lock\"] {\n\n    // [tablet portrait -]: Omit scroll bubbling\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Grid container - this class is applied to wrapper elements within the\n// header, content area and footer, and makes sure that their width is limited\n// to `1220px`, and they are rendered centered if the screen is larger.\n.md-grid {\n  max-width: px2rem(1220px);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n// Main container\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n}\n\n// Main area - stretch to remaining space of container\n.md-main {\n  flex-grow: 1;\n\n  // Main area wrapper\n  &__inner {\n    display: flex;\n    height: 100%;\n    margin-top: px2rem(24px + 6px);\n  }\n}\n\n// Add ellipsis in case of overflowing text\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: navigational elements\n// ----------------------------------------------------------------------------\n\n// Toggle - this class is applied to the checkbox elements, which are used to\n// implement the CSS-only drawer and navigation, as well as the search\n.md-toggle {\n  display: none;\n}\n\n// Skip link\n.md-skip {\n  position: fixed;\n  // Hack: if we don't set the negative `z-index`, the skip link will force the\n  // creation of new layers when code blocks are near the header on scrolling\n  z-index: -1;\n  margin: px2rem(10px);\n  padding: px2rem(6px) px2rem(10px);\n  color: var(--md-default-bg-color);\n  font-size: px2rem(12.8px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(px2rem(8px));\n  opacity: 0;\n\n  // Show skip link on focus\n  &:focus {\n    z-index: 10;\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n      opacity   175ms 75ms;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: print styles\n// ----------------------------------------------------------------------------\n\n// Add margins to page\n@page {\n  margin: 25mm;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Announcement bar\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide announcement bar\n  @media print {\n    display: none;\n  }\n\n  // Announcement wrapper\n  &__inner {\n    margin: px2rem(12px) auto;\n    padding: 0 px2rem(16px);\n    color: var(--md-footer-fg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Button to copy to clipboard\n.md-clipboard {\n  position: absolute;\n  top: px2em(8px);\n  right: px2em(8px);\n  z-index: 1;\n  width: px2em(24px);\n  height: px2em(24px);\n  color: var(--md-default-fg-color--lightest);\n  border-radius: px2rem(2px);\n  cursor: pointer;\n  transition: color 250ms;\n\n  // [print]: Hide button\n  @media print {\n    display: none;\n  }\n\n  // Hide outline for pointer devices\n  &:not(.focus-visible) {\n    outline: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  // Darken color on code block hover\n  :hover > & {\n    color: var(--md-default-fg-color--light);\n  }\n\n  // Button on focus/hover\n  &:focus,\n  &:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Button icon - the width and height are defined in `em`, so the size is\n  // automatically adjusted for nested code blocks (e.g. in admonitions)\n  &::after {\n    display: block;\n    width: px2em(18px);\n    height: px2em(18px);\n    margin: 0 auto;\n    background-color: currentColor;\n    mask-image: var(--md-clipboard-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n\n  // Inline button\n  &--inline {\n    cursor: pointer;\n\n    // Code block\n    code {\n      transition:\n        color            250ms,\n        background-color 250ms;\n    }\n\n    // Code block on focus/hover\n    &:focus code,\n    &:hover code {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Content area\n.md-content {\n  flex-grow: 1;\n  // Hack: we must use `overflow: hidden`, so the content area is capped by\n  // the dimensions of its parent. Otherwise, long code blocks might lead to\n  // a wider content area which will break everything. This, however, induces\n  // margin collapse, which will break scroll margins. Adding a large enough\n  // scroll padding seems to do the trick, at least in Chrome and Firefox.\n  overflow: hidden;\n  scroll-padding-top: px2rem(1024px);\n\n  // Content wrapper\n  &__inner {\n    margin: 0 px2rem(16px) px2rem(24px);\n    padding-top: px2rem(12px);\n\n    // [screen +]: Adjust spacing between content area and sidebars\n    @include break-from-device(screen) {\n\n      // Sidebar with navigation is visible\n      .md-sidebar--primary:not([hidden]) ~ .md-content > & {\n        margin-left: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(24px);\n          margin-left: px2rem(16px);\n        }\n      }\n\n      // Sidebar with table of contents is visible\n      .md-sidebar--secondary:not([hidden]) ~ .md-content > & {\n        margin-right: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(16px);\n          margin-left: px2rem(24px);\n        }\n      }\n    }\n\n    // Hack: add pseudo element for spacing, as the overflow of the content\n    // container may not be hidden due to an imminent offset error on targets\n    &::before {\n      display: block;\n      height: px2rem(8px);\n      content: \"\";\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  // Button inside of the content area - these buttons are meant for actions on\n  // a document-level, i.e. linking to related source code files, printing etc.\n  &__button {\n    float: right;\n    margin: px2rem(8px) 0;\n    margin-left: px2rem(8px);\n    padding: 0;\n\n    // [print]: Hide buttons\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: left;\n      margin-right: px2rem(8px);\n      margin-left: initial;\n\n      // Flip icon vertically\n      svg {\n        transform: scaleX(-1);\n      }\n    }\n\n    // Adjust default link color for icons\n    .md-typeset & {\n      color: var(--md-default-fg-color--lighter);\n    }\n\n    // Align with body copy located next to icon\n    svg {\n      display: inline;\n      vertical-align: top;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Dialog\n.md-dialog {\n  @include z-depth(2);\n\n  position: fixed;\n  right: px2rem(16px);\n  bottom: px2rem(16px);\n  left: initial;\n  z-index: 2;\n  min-width: px2rem(222px);\n  padding: px2rem(8px) px2rem(12px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(100%);\n  opacity: 0;\n  transition:\n    transform 0ms   400ms,\n    opacity   400ms;\n  pointer-events: none;\n\n  // [print]: Hide dialog\n  @media print {\n    display: none;\n  }\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    right: initial;\n    left: px2rem(16px);\n  }\n\n  // Dialog in open state\n  &[data-md-state=\"open\"] {\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),\n      opacity   400ms;\n    pointer-events: initial;\n  }\n\n  // Dialog wrapper\n  &__inner {\n    color: var(--md-default-bg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Form button\n  .md-button {\n    display: inline-block;\n    padding: px2em(10px) px2em(32px);\n    color: var(--md-primary-fg-color);\n    font-weight: 700;\n    border: px2rem(2px) solid currentColor;\n    border-radius: px2rem(2px);\n    transition:\n      color            125ms,\n      background-color 125ms,\n      border-color     125ms;\n\n    // Primary button\n    &--primary {\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color);\n      border-color: var(--md-primary-fg-color);\n    }\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-bg-color);\n      background-color: var(--md-accent-fg-color);\n      border-color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Form input\n  .md-input {\n    height: px2rem(36px);\n    padding: 0 px2rem(12px);\n    font-size: px2rem(16px);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    transition: box-shadow 250ms;\n\n    // Input on focus/hover\n    &:focus,\n    &:hover {\n      box-shadow:\n        0 px2rem(8px)   px2rem(20px) hsla(0, 0%, 0%, 0.15),\n        0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.15);\n    }\n\n    // Stretch to full width\n    &--stretch {\n      width: 100%;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Header - by default, the header will be sticky and stay always on top of the\n// viewport. If this behavior is not desired, just set `position: static`.\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  // Hack: reduce jitter by adding a transparent box shadow of the same size\n  // so the size of the layer doesn't change during animation\n  box-shadow:\n    0 0           px2rem(4px) rgba(0, 0, 0, 0),\n    0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);\n  transition:\n    color            250ms,\n    background-color 250ms;\n\n  // [print]: Hide header\n  @media print {\n    display: none;\n  }\n\n  // Header in shadow state, i.e. shadow is visible\n  &[data-md-state=\"shadow\"] {\n    box-shadow:\n      0 0           px2rem(4px) rgba(0, 0, 0, 0.1),\n      0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);\n    transition:\n      transform        250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n  }\n\n  // Header in hidden state, i.e. moved out of sight\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(-100%);\n    transition:\n      transform        250ms cubic-bezier(0.8, 0, 0.6, 1),\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n  }\n\n  // Header wrapper\n  &__inner {\n    display: flex;\n    align-items: center;\n    padding: 0 px2rem(4px);\n  }\n\n  // Header button\n  &__button {\n    position: relative;\n    z-index: 1;\n    display: inline-block;\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n    color: currentColor;\n    vertical-align: middle;\n    cursor: pointer;\n    transition: opacity 250ms;\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n    }\n\n    // Button with logo, pointing to `config.site_url`\n    &.md-logo {\n      margin: px2rem(4px);\n      padding: px2rem(8px);\n\n      // [tablet -]: Hide button\n      @include break-to-device(tablet) {\n        display: none;\n      }\n\n      // Image or icon\n      img,\n      svg {\n        display: block;\n        width: px2rem(24px);\n        height: px2rem(24px);\n        fill: currentColor;\n      }\n    }\n\n    // Button for search\n    &[for=\"__search\"] {\n\n      // [tablet landscape +]: Hide button\n      @include break-from-device(tablet landscape) {\n        display: none;\n      }\n\n      // [no-js]: Hide button\n      .no-js & {\n        display: none;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Button for drawer\n    &[for=\"__drawer\"] {\n\n      // [screen +]: Hide button\n      @include break-from-device(screen) {\n        display: none;\n      }\n    }\n  }\n\n  // Header topic\n  &__topic {\n    position: absolute;\n    display: flex;\n    max-width: 100%;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   150ms;\n\n    // Second header topic - title of the current page\n    & + & {\n      z-index: -1;\n      transform: translateX(px2rem(25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(-25px));\n      }\n    }\n  }\n\n  // Header title\n  &__title {\n    flex-grow: 1;\n    height: px2rem(48px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n\n    // Header title in active state, i.e. page title is visible\n    &[data-md-state=\"active\"] .md-header__topic {\n      z-index: -1;\n      transform: translateX(px2rem(-25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(25px));\n      }\n\n      // Second header topic - title of the current page\n      + .md-header__topic {\n        z-index: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms;\n        pointer-events: initial;\n      }\n    }\n\n    // Add ellipsis in case of overflowing text\n    > .md-header__ellipsis {\n      position: relative;\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  // Header options\n  &__options {\n    display: flex;\n    flex-shrink: 0;\n    max-width: 100%;\n    white-space: nowrap;\n    transition:\n      max-width  0ms 250ms,\n      opacity  250ms 250ms;\n\n    // Hide inactive buttons\n    > [data-md-state=\"hidden\"] {\n      display: none;\n    }\n\n    // Hide toggle when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      max-width: 0;\n      opacity: 0;\n      transition:\n        max-width 0ms,\n        opacity   0ms;\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n\n    // [tablet landscape +]: Show repository information\n    @include break-from-device(tablet landscape) {\n      display: block;\n      width: px2rem(234px);\n      max-width: px2rem(234px);\n      margin-left: px2rem(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(20px);\n        margin-left: initial;\n      }\n    }\n\n    // [screen +]: Adjust spacing of search bar\n    @include break-from-device(screen) {\n      margin-left: px2rem(28px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(28px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Footer\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide footer\n  @media print {\n    display: none;\n  }\n\n  // Footer wrapper\n  &__inner {\n    padding: px2rem(4px);\n    overflow: auto;\n  }\n\n  // Footer link to previous and next page\n  &__link {\n    display: flex;\n    padding-top: px2rem(28px);\n    padding-bottom: px2rem(8px);\n    transition: opacity 250ms;\n\n    // [tablet +]: Adjust width to 50/50\n    @include break-from-device(tablet) {\n      width: 50%;\n    }\n\n    // Footer link on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Footer link to previous page\n    &--prev {\n      float: left;\n\n      // [mobile -]: Adjust width to 25/75 and hide title\n      @include break-to-device(mobile) {\n        width: 25%;\n\n        // Hide footer title\n        .md-footer__title {\n          display: none;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Footer link to next page\n    &--next {\n      float: right;\n      text-align: right;\n\n      // [mobile -]: Adjust width to 25/75\n      @include break-to-device(mobile) {\n        width: 75%;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        text-align: left;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n\n  // Footer title\n  &__title {\n    position: relative;\n    flex-grow: 1;\n    max-width: calc(100% - #{px2rem(48px)});\n    padding: 0 px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n  }\n\n  // Footer link button\n  &__button {\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n  }\n\n  // Footer link direction (i.e. prev and next)\n  &__direction {\n    position: absolute;\n    right: 0;\n    left: 0;\n    margin-top: px2rem(-20px);\n    padding: 0 px2rem(20px);\n    font-size: px2rem(12.8px);\n    opacity: 0.7;\n  }\n}\n\n// Footer metadata\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n\n  // Footer metadata wrapper\n  &__inner {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    padding: px2rem(4px);\n  }\n\n  // Lighten color for non-hovered text links\n  html &.md-typeset a {\n    color: var(--md-footer-fg-color--light);\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-footer-fg-color);\n    }\n  }\n}\n\n// Footer copyright and theme information\n.md-footer-copyright {\n  width: 100%;\n  margin: auto px2rem(12px);\n  padding: px2rem(8px) 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: px2rem(12.8px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    width: auto;\n  }\n\n  // Footer copyright highlight - this is the upper part of the copyright and\n  // theme information, which will include a darker color than the theme link\n  &__highlight {\n    color: var(--md-footer-fg-color--light);\n  }\n}\n\n// Footer social links\n.md-footer-social {\n  margin: 0 px2rem(8px);\n  padding: px2rem(4px) 0 px2rem(12px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    padding: px2rem(12px) 0;\n  }\n\n  // Footer social link\n  &__link {\n    display: inline-block;\n    width: px2rem(32px);\n    height: px2rem(32px);\n    text-align: center;\n\n    // Adjust line-height to match height for correct alignment\n    &::before {\n      line-height: 1.9;\n    }\n\n    // Fill icon with current color\n    svg {\n      max-height: px2rem(16px);\n      vertical-align: -25%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Navigation\n.md-nav {\n  font-size: px2rem(14px);\n  line-height: 1.3;\n\n  // Navigation title\n  &__title {\n    display: block;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n\n    // Navigaton button\n    .md-nav__button {\n      display: none;\n\n      // Stretch images based on height, as it's the smaller dimension\n      img {\n        width: auto;\n        height: 100%;\n      }\n\n      // Button with logo, pointing to `config.site_url`\n      &.md-logo {\n\n        // Image or icon\n        img,\n        svg {\n          display: block;\n          width: px2rem(48px);\n          height: px2rem(48px);\n          fill: currentColor;\n        }\n      }\n    }\n  }\n\n  // Navigation list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Navigation item\n  &__item {\n    padding: 0 px2rem(12px);\n\n    // Navigation item on level 2\n    & & {\n      padding-right: 0;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(12px);\n        padding-left: 0;\n      }\n    }\n  }\n\n  // Navigation link\n  &__link {\n    display: block;\n    margin-top: 0.625em;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    cursor: pointer;\n    transition: color 125ms;\n    scroll-snap-align: start;\n\n    // Link in blurred state\n    &[data-md-state=\"blur\"] {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Active link\n    .md-nav__item &--active {\n      color: var(--md-typeset-a-color);\n    }\n\n    // Navigation link in nested list\n    .md-nav__item--nested > & {\n      color: inherit;\n    }\n\n    // Navigation link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Navigation link to table of contents\n    .md-nav--primary &[for=\"__toc\"] {\n      display: none;\n\n      // Table of contents icon\n      .md-icon::after {\n        display: block;\n        width: 100%;\n        height: 100%;\n        mask-image: var(--md-toc-icon);\n        background-color: currentColor;\n      }\n\n      // Hide table of contents\n      ~ .md-nav {\n        display: none;\n      }\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Primary and nested navigation\n    &--primary,\n    &--primary & {\n      position: absolute;\n      top: 0;\n      right: 0;\n      left: 0;\n      z-index: 1;\n      display: flex;\n      flex-direction: column;\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n    }\n\n    // Primary navigation\n    &--primary {\n\n      // Navigation title and item\n      .md-nav__title,\n      .md-nav__item {\n        font-size: px2rem(16px);\n        line-height: 1.5;\n      }\n\n      // Navigation title\n      .md-nav__title {\n        position: relative;\n        height: px2rem(112px);\n        padding: px2rem(60px) px2rem(16px) px2rem(4px);\n        color: var(--md-default-fg-color--light);\n        font-weight: 400;\n        line-height: px2rem(48px);\n        white-space: nowrap;\n        background-color: var(--md-default-fg-color--lightest);\n        cursor: pointer;\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: px2rem(8px);\n          left: px2rem(8px);\n          display: block;\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin: px2rem(4px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(8px);\n            left: initial;\n          }\n\n          // Navigation icon in link to previous level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--prev);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n\n        // Navigation list\n        ~ .md-nav__list {\n          overflow-y: auto;\n          background-color: var(--md-default-bg-color);\n          box-shadow:\n            0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;\n          scroll-snap-type: y mandatory;\n          touch-action: pan-y;\n\n          // Omit border on first child\n          > :first-child {\n            border-top: 0;\n          }\n        }\n\n        // Top-level navigation title\n        &[for=\"__drawer\"] {\n          color: var(--md-primary-bg-color);\n          background-color: var(--md-primary-fg-color);\n        }\n\n        // Button with logo, pointing to `config.site_url`\n        .md-logo {\n          position: absolute;\n          top: px2rem(4px);\n          left: px2rem(4px);\n          display: block;\n          margin: px2rem(4px);\n          padding: px2rem(8px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(4px);\n            left: initial;\n          }\n        }\n      }\n\n      // Navigation list\n      .md-nav__list {\n        flex: 1;\n      }\n\n      // Navigation item\n      .md-nav__item {\n        padding: 0;\n        border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n\n        // Navigation link in nested navigation\n        &--nested > .md-nav__link {\n          padding-right: px2rem(48px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            padding-right: px2rem(16px);\n            padding-left: px2rem(48px);\n          }\n        }\n\n        // Navigation link in active navigation\n        &--active > .md-nav__link {\n          color: var(--md-typeset-a-color);\n\n          // Navigation link on focus/hover\n          &:focus,\n          &:hover {\n            color: var(--md-accent-fg-color);\n          }\n        }\n      }\n\n      // Navigation link\n      .md-nav__link {\n        position: relative;\n        margin-top: 0;\n        padding: px2rem(12px) px2rem(16px);\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: 50%;\n          right: px2rem(12px);\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin-top: px2rem(-12px);\n          color: inherit;\n          font-size: px2rem(24px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: initial;\n            left: px2rem(12px);\n          }\n\n          // Navigation icon in link to next level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--next);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n      }\n\n      // Flip icon vertically\n      .md-nav__icon {\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] &::after {\n          transform: scale(-1);\n        }\n      }\n\n      // Table of contents contained in primary navigation\n      .md-nav--secondary {\n\n        // Navigation link - omit unnecessary layering\n        .md-nav__link {\n          position: static;\n        }\n\n        // Navigation on level 2-6\n        .md-nav {\n          position: static;\n          background-color: transparent;\n\n          // Navigation link on level 3\n          .md-nav__link {\n            padding-left: px2rem(28px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(28px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 4\n          .md-nav .md-nav__link {\n            padding-left: px2rem(40px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(40px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 5\n          .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(52px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(52px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 6\n          .md-nav .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(64px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(64px);\n              padding-left: initial;\n            }\n          }\n        }\n      }\n    }\n\n    // Table of contents\n    &--secondary {\n      background-color: transparent;\n    }\n\n    // Toggle for nested navigation\n    &__toggle ~ & {\n      display: flex;\n      transform: translateX(100%);\n      opacity: 0;\n      transition:\n        transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n        opacity   125ms 50ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(-100%);\n      }\n    }\n\n    // Show nested navigation when toggle is active\n    &__toggle:checked ~ & {\n      transform: translateX(0);\n      opacity: 1;\n      transition:\n        transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   125ms 125ms;\n\n      // Navigation list\n      > .md-nav__list {\n        // Hack: promote to own layer to reduce jitter\n        backface-visibility: hidden;\n      }\n    }\n  }\n\n  // [tablet portrait -]: Layered navigation with table of contents\n  @include break-to-device(tablet portrait) {\n\n    // Show link to table of contents\n    &--primary &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n\n    // Repository information container\n    &__source {\n      display: block;\n      padding: 0 px2rem(4px);\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color--dark);\n    }\n  }\n\n  // [tablet landscape]: Layered navigation with table of contents\n  @include break-at-device(tablet landscape) {\n\n    // Show link to integrated table of contents\n    &--integrated &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n      scroll-snap-align: initial;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n  }\n\n  // [tablet landscape +]: Tree-like table of contents\n  @include break-from-device(tablet landscape) {\n\n    // Navigation title\n    &--secondary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__toc\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n  }\n\n  // [screen +]: Tree-like navigation\n  @include break-from-device(screen) {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n\n    // Navigation title\n    &--primary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__drawer\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n\n    // Hide toggle for nested navigation\n    &__toggle ~ & {\n      display: none;\n    }\n\n    // Show nested navigation when toggle is active or indeterminate\n    &__toggle:checked ~ &,\n    &__toggle:indeterminate ~ & {\n      display: block;\n    }\n\n    // Hide navigation title in nested navigation\n    &__item--nested > & > &__title {\n      display: none;\n    }\n\n    // Navigation section\n    &__item--section {\n      display: block;\n      margin: 1.25em 0;\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Hide navigation link, as sections are always expanded\n      > .md-nav__link {\n        display: none;\n      }\n\n      // Navigation\n      > .md-nav {\n        display: block;\n\n        // Navigation title\n        > .md-nav__title {\n          display: block;\n          padding: 0;\n          pointer-events: none;\n          scroll-snap-align: start;\n        }\n\n        // Adjust spacing on next level item\n        > .md-nav__list > .md-nav__item {\n          padding: 0;\n        }\n      }\n    }\n\n    // Navigation icon\n    &__icon {\n      float: right;\n      width: px2rem(18px);\n      height: px2rem(18px);\n      transition: transform 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        transform: rotate(180deg);\n      }\n\n      // Navigation icon content\n      &::after {\n        display: inline-block;\n        width: 100%;\n        height: 100%;\n        vertical-align: px2rem(-2px);\n        background-color: currentColor;\n        mask-image: var(--md-nav-icon--next);\n        mask-repeat: no-repeat;\n        mask-size: contain;\n        content: \"\";\n      }\n\n      // Navigation icon - rotate icon when toggle is active or indeterminate\n      .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,\n      .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {\n        transform: rotate(90deg);\n      }\n    }\n\n    // Modifier for when navigation tabs are rendered\n    &--lifted {\n\n      // Hide nested items on level 1 and site title\n      > .md-nav__list > .md-nav__item--nested,\n      > .md-nav__title {\n        display: none;\n      }\n\n      // Hide level 1 items\n      > .md-nav__list > .md-nav__item {\n        display: none;\n\n        // Active parent navigation item\n        &--active {\n          display: block;\n          padding: 0;\n\n          // Hide nested links\n          > .md-nav__link {\n            display: none;\n          }\n\n          // Show title and adjust spacing\n          > .md-nav > .md-nav__title {\n            display: block;\n            padding: 0 px2rem(12px);\n            pointer-events: none;\n            scroll-snap-align: start;\n          }\n        }\n\n        // Adjust spacing for navigation item on level 2\n        > .md-nav__item {\n          padding-right: px2rem(12px);\n        }\n      }\n\n      // Hack: Always show active navigation tab on breakpoint screen, despite\n      // of checkbox being checked or not. Fixes #1655.\n      .md-nav[data-md-level=\"1\"] {\n        display: block;\n      }\n    }\n\n    // Modifier for when table of contents is rendered in primary navigation\n    &--integrated &__link[for=\"__toc\"] ~ .md-nav {\n      display: block;\n      margin-bottom: 1.25em;\n      border-left: px2rem(1px) solid var(--md-primary-fg-color);\n\n      // Hide navigation title\n      > .md-nav__title {\n        display: none;\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Search\n.md-search {\n  position: relative;\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n    padding: px2rem(4px) 0;\n  }\n\n  // [no-js]: Hide search\n  .no-js & {\n    display: none;\n  }\n\n  // Search overlay\n  &__overlay {\n    z-index: 1;\n    opacity: 0;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: absolute;\n      top: px2rem(4px);\n      left: px2rem(-44px);\n      width: px2rem(40px);\n      height: px2rem(40px);\n      overflow: hidden;\n      background-color: var(--md-default-bg-color);\n      border-radius: px2rem(20px);\n      transform-origin: center;\n      transition:\n        transform 300ms 100ms,\n        opacity   200ms 200ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-44px);\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        opacity: 1;\n        transition:\n          transform 400ms,\n          opacity   100ms;\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 0;\n      height: 0;\n      background-color: hsla(0, 0%, 0%, 0.54);\n      cursor: pointer;\n      transition:\n        width     0ms 250ms,\n        height    0ms 250ms,\n        opacity 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: 0;\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        width: 100%;\n        // Hack: when the header is translated upon scrolling, a new layer is\n        // induced, which means that the height will now refer to the height of\n        // the header, albeit positioning is fixed. This should be mitigated\n        // in all cases when setting the height to 2x the viewport.\n        height: 200vh;\n        opacity: 1;\n        transition:\n          width     0ms,\n          height    0ms,\n          opacity 250ms;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [mobile portrait -]: Scale up 45 times\n      @include break-to-device(mobile portrait) {\n        transform: scale(45);\n      }\n\n      // [mobile landscape]: Scale up 60 times\n      @include break-at-device(mobile landscape) {\n        transform: scale(60);\n      }\n\n      // [tablet portrait]: Scale up 75 times\n      @include break-at-device(tablet portrait) {\n        transform: scale(75);\n      }\n    }\n  }\n\n  // Search wrapper\n  &__inner {\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n      top: 0;\n      left: 100%;\n      z-index: 2;\n      width: 100%;\n      height: 100%;\n      transform: translateX(5%);\n      opacity: 0;\n      transition:\n        right       0ms 300ms,\n        left        0ms 300ms,\n        transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   150ms 150ms;\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        left: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          right       0ms   0ms,\n          left        0ms   0ms,\n          transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms 150ms;\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: 0;\n          left: initial;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      html [dir=\"rtl\"] & {\n        right: 100%;\n        left: initial;\n        transform: translateX(-5%);\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: relative;\n      float: right;\n      width: px2rem(234px);\n      padding: px2rem(2px) 0;\n      transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [tablet landscape]: Omit overlaying header title\n      @include break-at-device(tablet landscape) {\n        width: px2rem(468px);\n      }\n\n      // [screen +]: Match width of content area\n      @include break-from-device(screen) {\n        width: px2rem(688px);\n      }\n    }\n  }\n\n  // Search form\n  &__form {\n    position: relative;\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      border-radius: px2rem(2px);\n    }\n  }\n\n  // Search input\n  &__input {\n    position: relative;\n    z-index: 2;\n    padding: 0 px2rem(44px) 0 px2rem(72px);\n    text-overflow: ellipsis;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0 px2rem(12px) transparent;\n    transition:\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: 0 px2rem(72px) 0 px2rem(44px);\n    }\n\n    // Search placeholder\n    &::placeholder {\n      transition: color 250ms;\n    }\n\n    // Search icon and placeholder\n    ~ .md-search__icon,\n    &::placeholder {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Remove the \"x\" rendered by Internet Explorer\n    &::-ms-clear {\n      display: none;\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);\n    }\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      width: 100%;\n      height: px2rem(48px);\n      font-size: px2rem(18px);\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      width: 100%;\n      height: px2rem(36px);\n      padding-left: px2rem(44px);\n      color: inherit;\n      font-size: px2rem(16px);\n      background-color: hsla(0, 0%, 0%, 0.26);\n      border-radius: px2rem(2px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n      }\n\n      // Search icon\n      + .md-search__icon {\n        color: var(--md-primary-bg-color);\n      }\n\n      // Search placeholder\n      &::placeholder {\n        color: var(--md-primary-bg-color--light);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.12);\n      }\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        color: var(--md-default-fg-color);\n        text-overflow: clip;\n        background-color: var(--md-default-bg-color);\n        border-radius: px2rem(2px) px2rem(2px) 0 0;\n\n        // Search icon and placeholder\n        + .md-search__icon,\n        &::placeholder {\n          color: var(--md-default-fg-color--light);\n        }\n      }\n    }\n  }\n\n  // Search icon\n  &__icon {\n    position: absolute;\n    z-index: 2;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    cursor: pointer;\n    transition:\n      color   250ms,\n      opacity 250ms;\n\n    // Search icon on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Search focus button\n    &[for=\"__search\"] {\n      top: px2rem(6px);\n      left: px2rem(10px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(10px);\n        left: initial;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: px2rem(16px);\n          left: initial;\n        }\n\n        // Hide the magnifying glass\n        svg:first-child {\n          display: none;\n        }\n      }\n\n      // [tablet landscape +]: Header-embedded search\n      @include break-from-device(tablet landscape) {\n        pointer-events: none;\n\n        // Hide the back arrow\n        svg:last-child {\n          display: none;\n        }\n      }\n    }\n\n    // Search reset button\n    &[type=\"reset\"] {\n      top: px2rem(6px);\n      right: px2rem(10px);\n      transform: scale(0.75);\n      opacity: 0;\n      transition:\n        transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(10px);\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        right: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: initial;\n          left: px2rem(16px);\n        }\n      }\n\n      // Show reset button when search is active and input non-empty\n      [data-md-toggle=\"search\"]:checked ~ .md-header\n      .md-search__input:valid ~ & {\n        transform: scale(1);\n        opacity: 1;\n        pointer-events: initial;\n\n        // Search focus icon\n        &:hover {\n          opacity: 0.7;\n        }\n      }\n    }\n  }\n\n  // Search output\n  &__output {\n    position: absolute;\n    z-index: 1;\n    width: 100%;\n    overflow: hidden;\n    border-radius: 0 0 px2rem(2px) px2rem(2px);\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      top: px2rem(48px);\n      bottom: 0;\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      top: px2rem(38px);\n      opacity: 0;\n      transition: opacity 400ms;\n\n      // Show output when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        @include z-depth(6);\n\n        opacity: 1;\n      }\n    }\n  }\n\n  // Search scroll wrapper\n  &__scrollwrap {\n    height: 100%;\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping\n    // seems to be something that is not ready for prime time on some browsers.\n    // scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n\n    // Mitigiate excessive repaints on non-retina devices\n    @media (max-resolution: 1dppx) {\n      transform: translateZ(0);\n    }\n\n    // [tablet landscape]: Set fixed width to omit unnecessary reflow\n    @include break-at-device(tablet landscape) {\n      width: px2rem(468px);\n    }\n\n    // [screen +]: Set fixed width to omit unnecessary reflow\n    @include break-from-device(screen) {\n      width: px2rem(688px);\n    }\n\n    // [tablet landscape +]: Limit height to viewport\n    @include break-from-device(tablet landscape) {\n      max-height: 0;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Show scroll wrapper when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        max-height: 75vh;\n      }\n\n      // Search scroll wrapper on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n}\n\n// Search result\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n\n  // Search result metadata\n  &__meta {\n    padding: 0 px2rem(16px);\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: px2rem(36px);\n    background-color: var(--md-default-fg-color--lightest);\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Search result list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Search result item\n  &__item {\n    box-shadow: 0 px2rem(-1px) 0 var(--md-default-fg-color--lightest);\n\n    // Omit border on first child\n    &:first-child {\n      box-shadow: none;\n    }\n  }\n\n  // Search result link\n  &__link {\n    display: block;\n    outline: none;\n    transition: background-color 250ms;\n    scroll-snap-align: start;\n\n    // Search result link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Adjust spacing on last child of last link\n    &:last-child p:last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Search result more link\n  &__more summary {\n    display: block;\n    padding: px2em(12px) px2rem(16px);\n    color: var(--md-typeset-a-color);\n    font-size: px2rem(12.8px);\n    outline: 0;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result more link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n\n    // Adjust transparency of less relevant results\n    ~ * > * {\n      opacity: 0.65;\n    }\n  }\n\n  // Search result article\n  &__article {\n    position: relative;\n    padding: 0 px2rem(16px);\n    overflow: hidden;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result article document\n    &--document {\n\n      // Search result title\n      .md-search-result__title {\n        margin: px2rem(11px) 0;\n        font-weight: 400;\n        font-size: px2rem(16px);\n        line-height: 1.4;\n      }\n    }\n  }\n\n  // Search result icon\n  &__icon {\n    position: absolute;\n    left: 0;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    margin: px2rem(10px);\n    color: var(--md-default-fg-color--light);\n\n    // [tablet portrait -]: Hide icon\n    @include break-to-device(tablet portrait) {\n      display: none;\n    }\n\n    // Search result icon content\n    &::after {\n      display: inline-block;\n      width: 100%;\n      height: 100%;\n      background-color: currentColor;\n      mask-image: var(--md-search-result-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: 0;\n      left: initial;\n\n      // Flip icon vertically\n      &::after {\n        transform: scaleX(-1);\n      }\n    }\n  }\n\n  // Search result title\n  &__title {\n    margin: 0.5em 0;\n    font-weight: 700;\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n  }\n\n  // Search result teaser\n  &__teaser {\n    display: -webkit-box;\n    max-height: px2rem(40px);\n    margin: 0.5em 0;\n    overflow: hidden;\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n    text-overflow: ellipsis;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 2;\n\n    // [mobile -]: Adjust number of lines\n    @include break-to-device(mobile) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // [tablet landscape]: Adjust number of lines\n    @include break-at-device(tablet landscape) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // Search term highlighting\n    mark {\n      text-decoration: underline;\n      background-color: transparent;\n    }\n  }\n\n  // Search result terms\n  &__terms {\n    margin: 0.5em 0;\n    font-size: px2rem(12.8px);\n    font-style: italic;\n  }\n\n  // Search term highlighting\n  mark {\n    color: var(--md-accent-fg-color);\n    background-color: transparent;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Selection\n.md-select {\n  position: relative;\n  z-index: 1;\n\n  // Selection bubble\n  &__inner {\n    position: absolute;\n    top: calc(100% - #{px2rem(4px)});\n    left: 50%;\n    max-height: 0;\n    margin-top: px2rem(4px);\n    color: var(--md-default-fg-color);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    transform: translate3d(-50%, px2rem(6px), 0);\n    opacity: 0;\n    transition:\n      transform  250ms 375ms,\n      opacity    250ms 250ms,\n      max-height   0ms 500ms;\n\n    // Selection bubble on parent focus/hover\n    .md-select:focus-within &,\n    .md-select:hover & {\n      max-height: px2rem(200px);\n      transform: translate3d(-50%, 0, 0);\n      opacity: 1;\n      transition:\n        transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity    250ms,\n        max-height 250ms;\n    }\n\n    // Selection bubble handle\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 50%;\n      width: 0;\n      height: 0;\n      margin-top: px2rem(-4px);\n      margin-left: px2rem(-4px);\n      border: px2rem(4px) solid transparent;\n      border-top: 0;\n      border-bottom-color: var(--md-default-bg-color);\n      content: \"\";\n    }\n  }\n\n  // Selection list\n  &__list {\n    max-height: inherit;\n    margin: 0;\n    padding: 0;\n    overflow: auto;\n    font-size: px2rem(16px);\n    list-style-type: none;\n    border-radius: px2rem(2px);\n  }\n\n  // Selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    cursor: pointer;\n    transition:\n      background-color 250ms,\n      color            250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Sidebar\n.md-sidebar {\n  position: sticky;\n  top: px2rem(48px);\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: px2rem(242px);\n  padding: px2rem(24px) 0;\n\n  // [print]: Hide sidebar\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Show navigation as drawer\n  @include break-to-device(tablet) {\n\n    // Primary sidebar with navigation\n    &--primary {\n      position: fixed;\n      top: 0;\n      left: px2rem(-242px);\n      z-index: 3;\n      display: block;\n      width: px2rem(242px);\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n      transform: translateX(0);\n      transition:\n        transform  250ms cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-242px);\n        left: initial;\n      }\n\n      // Show sidebar when drawer is active\n      [data-md-toggle=\"drawer\"]:checked ~ .md-container & {\n        @include z-depth(8);\n\n        transform: translateX(px2rem(242px));\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          transform: translateX(px2rem(-242px));\n        }\n      }\n\n      // Stretch scroll wrapper for primary sidebar\n      .md-sidebar__scrollwrap {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        margin: 0;\n        scroll-snap-type: none;\n        overflow: hidden;\n      }\n    }\n  }\n\n  // [screen +]: Show navigation as sidebar\n  @include break-from-device(screen) {\n    height: 0;\n\n    // [no-js]: Switch to native sticky behavior\n    .no-js & {\n      height: auto;\n    }\n  }\n\n  // Secondary sidebar with table of contents\n  &--secondary {\n    display: none;\n    order: 2;\n\n    // [tablet landscape +]: Show table of contents as sidebar\n    @include break-from-device(tablet landscape) {\n      height: 0;\n\n      // [no-js]: Switch to native sticky behavior\n      .no-js & {\n        height: auto;\n      }\n\n      // Sidebar is visible\n      &:not([hidden]) {\n        display: block;\n      }\n\n      // Ensure smooth scrolling on iOS\n      .md-sidebar__scrollwrap {\n        touch-action: pan-y;\n      }\n    }\n  }\n\n  // Sidebar scroll wrapper\n  &__scrollwrap {\n    margin: 0 px2rem(4px);\n    overflow-y: auto;\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container\n    // to the bottom if `scroll-snap-type` is set on the initial render. For\n    // this reason, we disable scroll snapping until this is resolved (#1667).\n    // scroll-snap-type: y mandatory;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n    // Sidebar scroll wrapper on hover\n    &:hover {\n      scrollbar-color: var(--md-accent-fg-color) transparent;\n    }\n\n    // Webkit scrollbar\n    &::-webkit-scrollbar {\n      width: px2rem(4px);\n      height: px2rem(4px);\n    }\n\n    // Webkit scrollbar thumb\n    &::-webkit-scrollbar-thumb {\n      background-color: var(--md-default-fg-color--lighter);\n\n      // Webkit scrollbar thumb on hover\n      &:hover {\n        background-color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n\n// [tablet -]: Show overlay on active drawer\n@include break-to-device(tablet) {\n\n  // Sidebar overlay\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: hsla(0, 0%, 0%, 0.54);\n    opacity: 0;\n    transition:\n      width     0ms 250ms,\n      height    0ms 250ms,\n      opacity 250ms;\n\n    // Show overlay when drawer is active\n    [data-md-toggle=\"drawer\"]:checked ~ & {\n      width: 100%;\n      height: 100%;\n      opacity: 1;\n      transition:\n        width     0ms,\n        height    0ms,\n        opacity 250ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Navigation tabs\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  transition: background-color 250ms;\n\n  // [print]: Hide tabs\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Hide tabs\n  @include break-to-device(tablet) {\n    display: none;\n  }\n\n  // Tabs in hidden state, i.e. when scrolling down\n  &[data-md-state=\"hidden\"] {\n    pointer-events: none;\n  }\n\n  // Navigation tabs list\n  &__list {\n    margin: 0;\n    margin-left: px2rem(4px);\n    padding: 0;\n    white-space: nowrap;\n    list-style: none;\n    contain: content;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(4px);\n      margin-left: initial;\n    }\n  }\n\n  // Navigation tabs item\n  &__item {\n    display: inline-block;\n    height: px2rem(48px);\n    padding-right: px2rem(12px);\n    padding-left: px2rem(12px);\n  }\n\n  // Navigation tabs link - could be defined as block elements and aligned via\n  // line height, but this would imply more repaints when scrolling\n  &__link {\n    display: block;\n    margin-top: px2rem(16px);\n    font-size: px2rem(14px);\n    // Hack: save a repaint when tabs are appearing on scrolling up\n    backface-visibility: hidden;\n    opacity: 0.7;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   250ms;\n\n    // Active link and link on focus/hover\n    &--active,\n    &:focus,\n    &:hover {\n      color: inherit;\n      opacity: 1;\n    }\n\n    // Delay transitions by a small amount\n    @for $i from 2 through 16 {\n      .md-tabs__item:nth-child(#{$i}) & {\n        transition-delay: 20ms * ($i - 1);\n      }\n    }\n\n    // Hide tabs upon scrolling - disable transition to minimizes repaints\n    // while scrolling down, while scrolling up seems to be okay\n    .md-tabs[data-md-state=\"hidden\"] & {\n      transform: translateY(50%);\n      opacity: 0;\n      transition:\n        transform 0ms 100ms,\n        opacity 100ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Version selection\n.md-version {\n  flex-shrink: 0;\n  height: px2rem(48px);\n  font-size: px2rem(16px);\n\n  // Current selection\n  &__current {\n    position: relative;\n    // Hack: in general, we would use `vertical-align` to align the version at\n    // the bottom with the title, but since the list uses absolute positioning,\n    // this won't work consistently. Furthermore, we would need to use inline\n    // positioning to align the links, which looks jagged.\n    top: px2rem(1px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(28px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(28px);\n      margin-left: px2rem(8px);\n    }\n\n    // Version selection icon\n    &::after {\n      display: inline-block;\n      width: px2rem(8px);\n      height: px2rem(12px);\n      margin-left: px2rem(8px);\n      background-color: currentColor;\n      mask-image: var(--md-version-icon);\n      mask-repeat: no-repeat;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n  }\n\n  // Version selection list\n  &__list {\n    position: absolute;\n    top: px2rem(3px);\n    z-index: 1;\n    max-height: px2rem(36px);\n    margin: px2rem(4px) px2rem(16px);\n    padding: 0;\n    overflow: auto;\n    color: var(--md-default-fg-color);\n    list-style-type: none;\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    opacity: 0;\n    transition:\n      max-height 0ms 500ms,\n      opacity  250ms 250ms;\n    scroll-snap-type: y mandatory;\n\n    // List on focus/hover\n    &:focus-within,\n    &:hover {\n      max-height: px2rem(200px);\n      opacity: 1;\n      transition:\n        max-height 250ms,\n        opacity    250ms;\n    }\n  }\n\n  // Version selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Version selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    white-space: nowrap;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n/// Admonition flavours\n$admonitions: (\n  note:                       pencil $clr-blue-a200,\n  abstract summary tldr:      text-subject $clr-light-blue-a400,\n  info todo:                  information $clr-cyan-a700,\n  tip hint important:         fire $clr-teal-a700,\n  success check done:         check-circle $clr-green-a700,\n  question help faq:          help-circle $clr-light-green-a700,\n  warning caution attention:  alert $clr-orange-a400,\n  failure fail missing:       close-circle $clr-red-a200,\n  danger error:               flash-circle $clr-red-a400,\n  bug:                        bug $clr-pink-a400,\n  example:                    format-list-numbered $clr-deep-purple-a200,\n  quote cite:                 format-quote-close $clr-grey\n) !default;\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  @each $names, $props in $admonitions {\n    --md-admonition-icon--#{nth($names, 1)}:\n      svg-load(\"material/#{nth($props, 1)}.svg\");\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Admonition\n  .admonition {\n    margin: px2em(20px, 12.8px) 0;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    color: var(--md-admonition-fg-color);\n    font-size: px2rem(12.8px);\n    page-break-inside: avoid;\n    background-color: var(--md-admonition-bg-color);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.05);\n\n    // [print]: Omit shadow as it may lead to rendering errors\n    @media print {\n      box-shadow: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for nested admonitions\n    .admonition {\n      margin: 1em 0;\n    }\n\n    // Adjust spacing for contained table wrappers\n    .md-typeset__scrollwrap {\n      margin: 1em px2rem(-12px);\n    }\n\n    // Adjust spacing for contained tables\n    .md-typeset__table {\n      padding: 0 px2rem(12px);\n    }\n\n    // Adjust spacing for single-child tabbed block container\n    > .tabbed-set:only-child {\n      margin-top: 0;\n    }\n\n    // Adjust spacing on last child\n    html & > :last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Admonition title\n  .admonition-title {\n    position: relative;\n    margin: 0 px2rem(-12px) 0 px2rem(-16px);\n    padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);\n    font-weight: 700;\n    background-color: transparentize($clr-blue-a200, 0.9);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin: 0 px2rem(-16px) 0 px2rem(-12px);\n      padding: px2rem(8px) px2rem(40px) px2rem(8px) px2rem(12px);\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for title-only admonitions\n    html &:last-child {\n      margin-bottom: 0;\n    }\n\n    // Admonition icon\n    &::before {\n      position: absolute;\n      left: px2rem(12px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: $clr-blue-a200;\n      mask-image: var(--md-admonition-icon--note);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(12px);\n        left: initial;\n      }\n    }\n\n    // Omit background on inline code blocks, as they don't go well with the\n    // pastelly tones applied to admonition titles\n    code {\n      margin: initial;\n      padding: initial;\n      color: currentColor;\n      background-color: transparent;\n      border-radius: initial;\n      box-shadow: none;\n    }\n\n    // Adjust spacing on last tabbed block container child - if the tabbed\n    // block container is the sole child, it looks better to omit the margin\n    + .tabbed-set:last-child {\n      margin-top: 0;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: flavours\n// ----------------------------------------------------------------------------\n\n@each $names, $props in $admonitions {\n  $name: nth($names, 1);\n  $tint: nth($props, 2);\n\n  // Admonition flavour\n  .md-typeset .admonition.#{$name} {\n    border-color: $tint;\n  }\n\n  // Admonition flavour title\n  .md-typeset .#{$name} > .admonition-title {\n    background-color: transparentize($tint, 0.9);\n    border-color: $tint;\n\n    // Admonition icon\n    &::before {\n      background-color: $tint;\n      mask-image: var(--md-admonition-icon--#{$name});\n      mask-repeat: no-repeat;\n      mask-size: contain;\n    }\n  }\n\n  // Define synonyms for flavours\n  @if length($names) > 1 {\n    @for $n from 2 through length($names) {\n      .#{nth($names, $n)} {\n        @extend .#{$name};\n      }\n    }\n  }\n}\n","// ==========================================================================\n//\n// Name:        UI Color Palette\n// Description: The color palette of material design.\n// Version:     2.3.1\n//\n// Author:      Denis Malinochkin\n// Git:         https://github.com/mrmlnc/material-color\n//\n// twitter:     @mrmlnc\n//\n// ==========================================================================\n\n\n//\n// List of base colors\n//\n\n// $clr-red\n// $clr-pink\n// $clr-purple\n// $clr-deep-purple\n// $clr-indigo\n// $clr-blue\n// $clr-light-blue\n// $clr-cyan\n// $clr-teal\n// $clr-green\n// $clr-light-green\n// $clr-lime\n// $clr-yellow\n// $clr-amber\n// $clr-orange\n// $clr-deep-orange\n// $clr-brown\n// $clr-grey\n// $clr-blue-grey\n// $clr-black\n// $clr-white\n\n\n//\n// Red\n//\n\n$clr-red-list: (\n  \"base\": #f44336,\n  \"50\":   #ffebee,\n  \"100\":  #ffcdd2,\n  \"200\":  #ef9a9a,\n  \"300\":  #e57373,\n  \"400\":  #ef5350,\n  \"500\":  #f44336,\n  \"600\":  #e53935,\n  \"700\":  #d32f2f,\n  \"800\":  #c62828,\n  \"900\":  #b71c1c,\n  \"a100\": #ff8a80,\n  \"a200\": #ff5252,\n  \"a400\": #ff1744,\n  \"a700\": #d50000\n);\n\n$clr-red:      map-get($clr-red-list, \"base\");\n\n$clr-red-50:   map-get($clr-red-list, \"50\");\n$clr-red-100:  map-get($clr-red-list, \"100\");\n$clr-red-200:  map-get($clr-red-list, \"200\");\n$clr-red-300:  map-get($clr-red-list, \"300\");\n$clr-red-400:  map-get($clr-red-list, \"400\");\n$clr-red-500:  map-get($clr-red-list, \"500\");\n$clr-red-600:  map-get($clr-red-list, \"600\");\n$clr-red-700:  map-get($clr-red-list, \"700\");\n$clr-red-800:  map-get($clr-red-list, \"800\");\n$clr-red-900:  map-get($clr-red-list, \"900\");\n$clr-red-a100: map-get($clr-red-list, \"a100\");\n$clr-red-a200: map-get($clr-red-list, \"a200\");\n$clr-red-a400: map-get($clr-red-list, \"a400\");\n$clr-red-a700: map-get($clr-red-list, \"a700\");\n\n\n//\n// Pink\n//\n\n$clr-pink-list: (\n  \"base\": #e91e63,\n  \"50\":   #fce4ec,\n  \"100\":  #f8bbd0,\n  \"200\":  #f48fb1,\n  \"300\":  #f06292,\n  \"400\":  #ec407a,\n  \"500\":  #e91e63,\n  \"600\":  #d81b60,\n  \"700\":  #c2185b,\n  \"800\":  #ad1457,\n  \"900\":  #880e4f,\n  \"a100\": #ff80ab,\n  \"a200\": #ff4081,\n  \"a400\": #f50057,\n  \"a700\": #c51162\n);\n\n$clr-pink:      map-get($clr-pink-list, \"base\");\n\n$clr-pink-50:   map-get($clr-pink-list, \"50\");\n$clr-pink-100:  map-get($clr-pink-list, \"100\");\n$clr-pink-200:  map-get($clr-pink-list, \"200\");\n$clr-pink-300:  map-get($clr-pink-list, \"300\");\n$clr-pink-400:  map-get($clr-pink-list, \"400\");\n$clr-pink-500:  map-get($clr-pink-list, \"500\");\n$clr-pink-600:  map-get($clr-pink-list, \"600\");\n$clr-pink-700:  map-get($clr-pink-list, \"700\");\n$clr-pink-800:  map-get($clr-pink-list, \"800\");\n$clr-pink-900:  map-get($clr-pink-list, \"900\");\n$clr-pink-a100: map-get($clr-pink-list, \"a100\");\n$clr-pink-a200: map-get($clr-pink-list, \"a200\");\n$clr-pink-a400: map-get($clr-pink-list, \"a400\");\n$clr-pink-a700: map-get($clr-pink-list, \"a700\");\n\n\n//\n// Purple\n//\n\n$clr-purple-list: (\n  \"base\": #9c27b0,\n  \"50\":   #f3e5f5,\n  \"100\":  #e1bee7,\n  \"200\":  #ce93d8,\n  \"300\":  #ba68c8,\n  \"400\":  #ab47bc,\n  \"500\":  #9c27b0,\n  \"600\":  #8e24aa,\n  \"700\":  #7b1fa2,\n  \"800\":  #6a1b9a,\n  \"900\":  #4a148c,\n  \"a100\": #ea80fc,\n  \"a200\": #e040fb,\n  \"a400\": #d500f9,\n  \"a700\": #aa00ff\n);\n\n$clr-purple:      map-get($clr-purple-list, \"base\");\n\n$clr-purple-50:   map-get($clr-purple-list, \"50\");\n$clr-purple-100:  map-get($clr-purple-list, \"100\");\n$clr-purple-200:  map-get($clr-purple-list, \"200\");\n$clr-purple-300:  map-get($clr-purple-list, \"300\");\n$clr-purple-400:  map-get($clr-purple-list, \"400\");\n$clr-purple-500:  map-get($clr-purple-list, \"500\");\n$clr-purple-600:  map-get($clr-purple-list, \"600\");\n$clr-purple-700:  map-get($clr-purple-list, \"700\");\n$clr-purple-800:  map-get($clr-purple-list, \"800\");\n$clr-purple-900:  map-get($clr-purple-list, \"900\");\n$clr-purple-a100: map-get($clr-purple-list, \"a100\");\n$clr-purple-a200: map-get($clr-purple-list, \"a200\");\n$clr-purple-a400: map-get($clr-purple-list, \"a400\");\n$clr-purple-a700: map-get($clr-purple-list, \"a700\");\n\n\n//\n// Deep purple\n//\n\n$clr-deep-purple-list: (\n  \"base\": #673ab7,\n  \"50\":   #ede7f6,\n  \"100\":  #d1c4e9,\n  \"200\":  #b39ddb,\n  \"300\":  #9575cd,\n  \"400\":  #7e57c2,\n  \"500\":  #673ab7,\n  \"600\":  #5e35b1,\n  \"700\":  #512da8,\n  \"800\":  #4527a0,\n  \"900\":  #311b92,\n  \"a100\": #b388ff,\n  \"a200\": #7c4dff,\n  \"a400\": #651fff,\n  \"a700\": #6200ea\n);\n\n$clr-deep-purple:      map-get($clr-deep-purple-list, \"base\");\n\n$clr-deep-purple-50:   map-get($clr-deep-purple-list, \"50\");\n$clr-deep-purple-100:  map-get($clr-deep-purple-list, \"100\");\n$clr-deep-purple-200:  map-get($clr-deep-purple-list, \"200\");\n$clr-deep-purple-300:  map-get($clr-deep-purple-list, \"300\");\n$clr-deep-purple-400:  map-get($clr-deep-purple-list, \"400\");\n$clr-deep-purple-500:  map-get($clr-deep-purple-list, \"500\");\n$clr-deep-purple-600:  map-get($clr-deep-purple-list, \"600\");\n$clr-deep-purple-700:  map-get($clr-deep-purple-list, \"700\");\n$clr-deep-purple-800:  map-get($clr-deep-purple-list, \"800\");\n$clr-deep-purple-900:  map-get($clr-deep-purple-list, \"900\");\n$clr-deep-purple-a100: map-get($clr-deep-purple-list, \"a100\");\n$clr-deep-purple-a200: map-get($clr-deep-purple-list, \"a200\");\n$clr-deep-purple-a400: map-get($clr-deep-purple-list, \"a400\");\n$clr-deep-purple-a700: map-get($clr-deep-purple-list, \"a700\");\n\n\n//\n// Indigo\n//\n\n$clr-indigo-list: (\n  \"base\": #3f51b5,\n  \"50\":   #e8eaf6,\n  \"100\":  #c5cae9,\n  \"200\":  #9fa8da,\n  \"300\":  #7986cb,\n  \"400\":  #5c6bc0,\n  \"500\":  #3f51b5,\n  \"600\":  #3949ab,\n  \"700\":  #303f9f,\n  \"800\":  #283593,\n  \"900\":  #1a237e,\n  \"a100\": #8c9eff,\n  \"a200\": #536dfe,\n  \"a400\": #3d5afe,\n  \"a700\": #304ffe\n);\n\n$clr-indigo:      map-get($clr-indigo-list, \"base\");\n\n$clr-indigo-50:   map-get($clr-indigo-list, \"50\");\n$clr-indigo-100:  map-get($clr-indigo-list, \"100\");\n$clr-indigo-200:  map-get($clr-indigo-list, \"200\");\n$clr-indigo-300:  map-get($clr-indigo-list, \"300\");\n$clr-indigo-400:  map-get($clr-indigo-list, \"400\");\n$clr-indigo-500:  map-get($clr-indigo-list, \"500\");\n$clr-indigo-600:  map-get($clr-indigo-list, \"600\");\n$clr-indigo-700:  map-get($clr-indigo-list, \"700\");\n$clr-indigo-800:  map-get($clr-indigo-list, \"800\");\n$clr-indigo-900:  map-get($clr-indigo-list, \"900\");\n$clr-indigo-a100: map-get($clr-indigo-list, \"a100\");\n$clr-indigo-a200: map-get($clr-indigo-list, \"a200\");\n$clr-indigo-a400: map-get($clr-indigo-list, \"a400\");\n$clr-indigo-a700: map-get($clr-indigo-list, \"a700\");\n\n\n//\n// Blue\n//\n\n$clr-blue-list: (\n  \"base\": #2196f3,\n  \"50\":   #e3f2fd,\n  \"100\":  #bbdefb,\n  \"200\":  #90caf9,\n  \"300\":  #64b5f6,\n  \"400\":  #42a5f5,\n  \"500\":  #2196f3,\n  \"600\":  #1e88e5,\n  \"700\":  #1976d2,\n  \"800\":  #1565c0,\n  \"900\":  #0d47a1,\n  \"a100\": #82b1ff,\n  \"a200\": #448aff,\n  \"a400\": #2979ff,\n  \"a700\": #2962ff\n);\n\n$clr-blue:      map-get($clr-blue-list, \"base\");\n\n$clr-blue-50:   map-get($clr-blue-list, \"50\");\n$clr-blue-100:  map-get($clr-blue-list, \"100\");\n$clr-blue-200:  map-get($clr-blue-list, \"200\");\n$clr-blue-300:  map-get($clr-blue-list, \"300\");\n$clr-blue-400:  map-get($clr-blue-list, \"400\");\n$clr-blue-500:  map-get($clr-blue-list, \"500\");\n$clr-blue-600:  map-get($clr-blue-list, \"600\");\n$clr-blue-700:  map-get($clr-blue-list, \"700\");\n$clr-blue-800:  map-get($clr-blue-list, \"800\");\n$clr-blue-900:  map-get($clr-blue-list, \"900\");\n$clr-blue-a100: map-get($clr-blue-list, \"a100\");\n$clr-blue-a200: map-get($clr-blue-list, \"a200\");\n$clr-blue-a400: map-get($clr-blue-list, \"a400\");\n$clr-blue-a700: map-get($clr-blue-list, \"a700\");\n\n\n//\n// Light Blue\n//\n\n$clr-light-blue-list: (\n  \"base\": #03a9f4,\n  \"50\":   #e1f5fe,\n  \"100\":  #b3e5fc,\n  \"200\":  #81d4fa,\n  \"300\":  #4fc3f7,\n  \"400\":  #29b6f6,\n  \"500\":  #03a9f4,\n  \"600\":  #039be5,\n  \"700\":  #0288d1,\n  \"800\":  #0277bd,\n  \"900\":  #01579b,\n  \"a100\": #80d8ff,\n  \"a200\": #40c4ff,\n  \"a400\": #00b0ff,\n  \"a700\": #0091ea\n);\n\n$clr-light-blue:      map-get($clr-light-blue-list, \"base\");\n\n$clr-light-blue-50:   map-get($clr-light-blue-list, \"50\");\n$clr-light-blue-100:  map-get($clr-light-blue-list, \"100\");\n$clr-light-blue-200:  map-get($clr-light-blue-list, \"200\");\n$clr-light-blue-300:  map-get($clr-light-blue-list, \"300\");\n$clr-light-blue-400:  map-get($clr-light-blue-list, \"400\");\n$clr-light-blue-500:  map-get($clr-light-blue-list, \"500\");\n$clr-light-blue-600:  map-get($clr-light-blue-list, \"600\");\n$clr-light-blue-700:  map-get($clr-light-blue-list, \"700\");\n$clr-light-blue-800:  map-get($clr-light-blue-list, \"800\");\n$clr-light-blue-900:  map-get($clr-light-blue-list, \"900\");\n$clr-light-blue-a100: map-get($clr-light-blue-list, \"a100\");\n$clr-light-blue-a200: map-get($clr-light-blue-list, \"a200\");\n$clr-light-blue-a400: map-get($clr-light-blue-list, \"a400\");\n$clr-light-blue-a700: map-get($clr-light-blue-list, \"a700\");\n\n\n//\n// Cyan\n//\n\n$clr-cyan-list: (\n  \"base\": #00bcd4,\n  \"50\":   #e0f7fa,\n  \"100\":  #b2ebf2,\n  \"200\":  #80deea,\n  \"300\":  #4dd0e1,\n  \"400\":  #26c6da,\n  \"500\":  #00bcd4,\n  \"600\":  #00acc1,\n  \"700\":  #0097a7,\n  \"800\":  #00838f,\n  \"900\":  #006064,\n  \"a100\": #84ffff,\n  \"a200\": #18ffff,\n  \"a400\": #00e5ff,\n  \"a700\": #00b8d4\n);\n\n$clr-cyan:      map-get($clr-cyan-list, \"base\");\n\n$clr-cyan-50:   map-get($clr-cyan-list, \"50\");\n$clr-cyan-100:  map-get($clr-cyan-list, \"100\");\n$clr-cyan-200:  map-get($clr-cyan-list, \"200\");\n$clr-cyan-300:  map-get($clr-cyan-list, \"300\");\n$clr-cyan-400:  map-get($clr-cyan-list, \"400\");\n$clr-cyan-500:  map-get($clr-cyan-list, \"500\");\n$clr-cyan-600:  map-get($clr-cyan-list, \"600\");\n$clr-cyan-700:  map-get($clr-cyan-list, \"700\");\n$clr-cyan-800:  map-get($clr-cyan-list, \"800\");\n$clr-cyan-900:  map-get($clr-cyan-list, \"900\");\n$clr-cyan-a100: map-get($clr-cyan-list, \"a100\");\n$clr-cyan-a200: map-get($clr-cyan-list, \"a200\");\n$clr-cyan-a400: map-get($clr-cyan-list, \"a400\");\n$clr-cyan-a700: map-get($clr-cyan-list, \"a700\");\n\n\n//\n// Teal\n//\n\n$clr-teal-list: (\n  \"base\": #009688,\n  \"50\":   #e0f2f1,\n  \"100\":  #b2dfdb,\n  \"200\":  #80cbc4,\n  \"300\":  #4db6ac,\n  \"400\":  #26a69a,\n  \"500\":  #009688,\n  \"600\":  #00897b,\n  \"700\":  #00796b,\n  \"800\":  #00695c,\n  \"900\":  #004d40,\n  \"a100\": #a7ffeb,\n  \"a200\": #64ffda,\n  \"a400\": #1de9b6,\n  \"a700\": #00bfa5\n);\n\n$clr-teal:      map-get($clr-teal-list, \"base\");\n\n$clr-teal-50:   map-get($clr-teal-list, \"50\");\n$clr-teal-100:  map-get($clr-teal-list, \"100\");\n$clr-teal-200:  map-get($clr-teal-list, \"200\");\n$clr-teal-300:  map-get($clr-teal-list, \"300\");\n$clr-teal-400:  map-get($clr-teal-list, \"400\");\n$clr-teal-500:  map-get($clr-teal-list, \"500\");\n$clr-teal-600:  map-get($clr-teal-list, \"600\");\n$clr-teal-700:  map-get($clr-teal-list, \"700\");\n$clr-teal-800:  map-get($clr-teal-list, \"800\");\n$clr-teal-900:  map-get($clr-teal-list, \"900\");\n$clr-teal-a100: map-get($clr-teal-list, \"a100\");\n$clr-teal-a200: map-get($clr-teal-list, \"a200\");\n$clr-teal-a400: map-get($clr-teal-list, \"a400\");\n$clr-teal-a700: map-get($clr-teal-list, \"a700\");\n\n\n//\n// Green\n//\n\n$clr-green-list: (\n  \"base\": #4caf50,\n  \"50\":   #e8f5e9,\n  \"100\":  #c8e6c9,\n  \"200\":  #a5d6a7,\n  \"300\":  #81c784,\n  \"400\":  #66bb6a,\n  \"500\":  #4caf50,\n  \"600\":  #43a047,\n  \"700\":  #388e3c,\n  \"800\":  #2e7d32,\n  \"900\":  #1b5e20,\n  \"a100\": #b9f6ca,\n  \"a200\": #69f0ae,\n  \"a400\": #00e676,\n  \"a700\": #00c853\n);\n\n$clr-green:      map-get($clr-green-list, \"base\");\n\n$clr-green-50:   map-get($clr-green-list, \"50\");\n$clr-green-100:  map-get($clr-green-list, \"100\");\n$clr-green-200:  map-get($clr-green-list, \"200\");\n$clr-green-300:  map-get($clr-green-list, \"300\");\n$clr-green-400:  map-get($clr-green-list, \"400\");\n$clr-green-500:  map-get($clr-green-list, \"500\");\n$clr-green-600:  map-get($clr-green-list, \"600\");\n$clr-green-700:  map-get($clr-green-list, \"700\");\n$clr-green-800:  map-get($clr-green-list, \"800\");\n$clr-green-900:  map-get($clr-green-list, \"900\");\n$clr-green-a100: map-get($clr-green-list, \"a100\");\n$clr-green-a200: map-get($clr-green-list, \"a200\");\n$clr-green-a400: map-get($clr-green-list, \"a400\");\n$clr-green-a700: map-get($clr-green-list, \"a700\");\n\n\n//\n// Light green\n//\n\n$clr-light-green-list: (\n  \"base\": #8bc34a,\n  \"50\":   #f1f8e9,\n  \"100\":  #dcedc8,\n  \"200\":  #c5e1a5,\n  \"300\":  #aed581,\n  \"400\":  #9ccc65,\n  \"500\":  #8bc34a,\n  \"600\":  #7cb342,\n  \"700\":  #689f38,\n  \"800\":  #558b2f,\n  \"900\":  #33691e,\n  \"a100\": #ccff90,\n  \"a200\": #b2ff59,\n  \"a400\": #76ff03,\n  \"a700\": #64dd17\n);\n\n$clr-light-green:      map-get($clr-light-green-list, \"base\");\n\n$clr-light-green-50:   map-get($clr-light-green-list, \"50\");\n$clr-light-green-100:  map-get($clr-light-green-list, \"100\");\n$clr-light-green-200:  map-get($clr-light-green-list, \"200\");\n$clr-light-green-300:  map-get($clr-light-green-list, \"300\");\n$clr-light-green-400:  map-get($clr-light-green-list, \"400\");\n$clr-light-green-500:  map-get($clr-light-green-list, \"500\");\n$clr-light-green-600:  map-get($clr-light-green-list, \"600\");\n$clr-light-green-700:  map-get($clr-light-green-list, \"700\");\n$clr-light-green-800:  map-get($clr-light-green-list, \"800\");\n$clr-light-green-900:  map-get($clr-light-green-list, \"900\");\n$clr-light-green-a100: map-get($clr-light-green-list, \"a100\");\n$clr-light-green-a200: map-get($clr-light-green-list, \"a200\");\n$clr-light-green-a400: map-get($clr-light-green-list, \"a400\");\n$clr-light-green-a700: map-get($clr-light-green-list, \"a700\");\n\n\n//\n// Lime\n//\n\n$clr-lime-list: (\n  \"base\": #cddc39,\n  \"50\":   #f9fbe7,\n  \"100\":  #f0f4c3,\n  \"200\":  #e6ee9c,\n  \"300\":  #dce775,\n  \"400\":  #d4e157,\n  \"500\":  #cddc39,\n  \"600\":  #c0ca33,\n  \"700\":  #afb42b,\n  \"800\":  #9e9d24,\n  \"900\":  #827717,\n  \"a100\": #f4ff81,\n  \"a200\": #eeff41,\n  \"a400\": #c6ff00,\n  \"a700\": #aeea00\n);\n\n$clr-lime:      map-get($clr-lime-list, \"base\");\n\n$clr-lime-50:   map-get($clr-lime-list, \"50\");\n$clr-lime-100:  map-get($clr-lime-list, \"100\");\n$clr-lime-200:  map-get($clr-lime-list, \"200\");\n$clr-lime-300:  map-get($clr-lime-list, \"300\");\n$clr-lime-400:  map-get($clr-lime-list, \"400\");\n$clr-lime-500:  map-get($clr-lime-list, \"500\");\n$clr-lime-600:  map-get($clr-lime-list, \"600\");\n$clr-lime-700:  map-get($clr-lime-list, \"700\");\n$clr-lime-800:  map-get($clr-lime-list, \"800\");\n$clr-lime-900:  map-get($clr-lime-list, \"900\");\n$clr-lime-a100: map-get($clr-lime-list, \"a100\");\n$clr-lime-a200: map-get($clr-lime-list, \"a200\");\n$clr-lime-a400: map-get($clr-lime-list, \"a400\");\n$clr-lime-a700: map-get($clr-lime-list, \"a700\");\n\n\n//\n// Yellow\n//\n\n$clr-yellow-list: (\n  \"base\": #ffeb3b,\n  \"50\":   #fffde7,\n  \"100\":  #fff9c4,\n  \"200\":  #fff59d,\n  \"300\":  #fff176,\n  \"400\":  #ffee58,\n  \"500\":  #ffeb3b,\n  \"600\":  #fdd835,\n  \"700\":  #fbc02d,\n  \"800\":  #f9a825,\n  \"900\":  #f57f17,\n  \"a100\": #ffff8d,\n  \"a200\": #ffff00,\n  \"a400\": #ffea00,\n  \"a700\": #ffd600\n);\n\n$clr-yellow:      map-get($clr-yellow-list, \"base\");\n\n$clr-yellow-50:   map-get($clr-yellow-list, \"50\");\n$clr-yellow-100:  map-get($clr-yellow-list, \"100\");\n$clr-yellow-200:  map-get($clr-yellow-list, \"200\");\n$clr-yellow-300:  map-get($clr-yellow-list, \"300\");\n$clr-yellow-400:  map-get($clr-yellow-list, \"400\");\n$clr-yellow-500:  map-get($clr-yellow-list, \"500\");\n$clr-yellow-600:  map-get($clr-yellow-list, \"600\");\n$clr-yellow-700:  map-get($clr-yellow-list, \"700\");\n$clr-yellow-800:  map-get($clr-yellow-list, \"800\");\n$clr-yellow-900:  map-get($clr-yellow-list, \"900\");\n$clr-yellow-a100: map-get($clr-yellow-list, \"a100\");\n$clr-yellow-a200: map-get($clr-yellow-list, \"a200\");\n$clr-yellow-a400: map-get($clr-yellow-list, \"a400\");\n$clr-yellow-a700: map-get($clr-yellow-list, \"a700\");\n\n\n//\n// amber\n//\n\n$clr-amber-list: (\n  \"base\": #ffc107,\n  \"50\":   #fff8e1,\n  \"100\":  #ffecb3,\n  \"200\":  #ffe082,\n  \"300\":  #ffd54f,\n  \"400\":  #ffca28,\n  \"500\":  #ffc107,\n  \"600\":  #ffb300,\n  \"700\":  #ffa000,\n  \"800\":  #ff8f00,\n  \"900\":  #ff6f00,\n  \"a100\": #ffe57f,\n  \"a200\": #ffd740,\n  \"a400\": #ffc400,\n  \"a700\": #ffab00\n);\n\n$clr-amber:      map-get($clr-amber-list, \"base\");\n\n$clr-amber-50:   map-get($clr-amber-list, \"50\");\n$clr-amber-100:  map-get($clr-amber-list, \"100\");\n$clr-amber-200:  map-get($clr-amber-list, \"200\");\n$clr-amber-300:  map-get($clr-amber-list, \"300\");\n$clr-amber-400:  map-get($clr-amber-list, \"400\");\n$clr-amber-500:  map-get($clr-amber-list, \"500\");\n$clr-amber-600:  map-get($clr-amber-list, \"600\");\n$clr-amber-700:  map-get($clr-amber-list, \"700\");\n$clr-amber-800:  map-get($clr-amber-list, \"800\");\n$clr-amber-900:  map-get($clr-amber-list, \"900\");\n$clr-amber-a100: map-get($clr-amber-list, \"a100\");\n$clr-amber-a200: map-get($clr-amber-list, \"a200\");\n$clr-amber-a400: map-get($clr-amber-list, \"a400\");\n$clr-amber-a700: map-get($clr-amber-list, \"a700\");\n\n\n//\n// Orange\n//\n\n$clr-orange-list: (\n  \"base\": #ff9800,\n  \"50\":   #fff3e0,\n  \"100\":  #ffe0b2,\n  \"200\":  #ffcc80,\n  \"300\":  #ffb74d,\n  \"400\":  #ffa726,\n  \"500\":  #ff9800,\n  \"600\":  #fb8c00,\n  \"700\":  #f57c00,\n  \"800\":  #ef6c00,\n  \"900\":  #e65100,\n  \"a100\": #ffd180,\n  \"a200\": #ffab40,\n  \"a400\": #ff9100,\n  \"a700\": #ff6d00\n);\n\n$clr-orange:      map-get($clr-orange-list, \"base\");\n\n$clr-orange-50:   map-get($clr-orange-list, \"50\");\n$clr-orange-100:  map-get($clr-orange-list, \"100\");\n$clr-orange-200:  map-get($clr-orange-list, \"200\");\n$clr-orange-300:  map-get($clr-orange-list, \"300\");\n$clr-orange-400:  map-get($clr-orange-list, \"400\");\n$clr-orange-500:  map-get($clr-orange-list, \"500\");\n$clr-orange-600:  map-get($clr-orange-list, \"600\");\n$clr-orange-700:  map-get($clr-orange-list, \"700\");\n$clr-orange-800:  map-get($clr-orange-list, \"800\");\n$clr-orange-900:  map-get($clr-orange-list, \"900\");\n$clr-orange-a100: map-get($clr-orange-list, \"a100\");\n$clr-orange-a200: map-get($clr-orange-list, \"a200\");\n$clr-orange-a400: map-get($clr-orange-list, \"a400\");\n$clr-orange-a700: map-get($clr-orange-list, \"a700\");\n\n\n//\n// Deep orange\n//\n\n$clr-deep-orange-list: (\n  \"base\": #ff5722,\n  \"50\":   #fbe9e7,\n  \"100\":  #ffccbc,\n  \"200\":  #ffab91,\n  \"300\":  #ff8a65,\n  \"400\":  #ff7043,\n  \"500\":  #ff5722,\n  \"600\":  #f4511e,\n  \"700\":  #e64a19,\n  \"800\":  #d84315,\n  \"900\":  #bf360c,\n  \"a100\": #ff9e80,\n  \"a200\": #ff6e40,\n  \"a400\": #ff3d00,\n  \"a700\": #dd2c00\n);\n\n$clr-deep-orange:      map-get($clr-deep-orange-list, \"base\");\n\n$clr-deep-orange-50:   map-get($clr-deep-orange-list, \"50\");\n$clr-deep-orange-100:  map-get($clr-deep-orange-list, \"100\");\n$clr-deep-orange-200:  map-get($clr-deep-orange-list, \"200\");\n$clr-deep-orange-300:  map-get($clr-deep-orange-list, \"300\");\n$clr-deep-orange-400:  map-get($clr-deep-orange-list, \"400\");\n$clr-deep-orange-500:  map-get($clr-deep-orange-list, \"500\");\n$clr-deep-orange-600:  map-get($clr-deep-orange-list, \"600\");\n$clr-deep-orange-700:  map-get($clr-deep-orange-list, \"700\");\n$clr-deep-orange-800:  map-get($clr-deep-orange-list, \"800\");\n$clr-deep-orange-900:  map-get($clr-deep-orange-list, \"900\");\n$clr-deep-orange-a100: map-get($clr-deep-orange-list, \"a100\");\n$clr-deep-orange-a200: map-get($clr-deep-orange-list, \"a200\");\n$clr-deep-orange-a400: map-get($clr-deep-orange-list, \"a400\");\n$clr-deep-orange-a700: map-get($clr-deep-orange-list, \"a700\");\n\n\n//\n// Brown\n//\n\n$clr-brown-list: (\n  \"base\": #795548,\n  \"50\":   #efebe9,\n  \"100\":  #d7ccc8,\n  \"200\":  #bcaaa4,\n  \"300\":  #a1887f,\n  \"400\":  #8d6e63,\n  \"500\":  #795548,\n  \"600\":  #6d4c41,\n  \"700\":  #5d4037,\n  \"800\":  #4e342e,\n  \"900\":  #3e2723,\n);\n\n$clr-brown:     map-get($clr-brown-list, \"base\");\n\n$clr-brown-50:  map-get($clr-brown-list, \"50\");\n$clr-brown-100: map-get($clr-brown-list, \"100\");\n$clr-brown-200: map-get($clr-brown-list, \"200\");\n$clr-brown-300: map-get($clr-brown-list, \"300\");\n$clr-brown-400: map-get($clr-brown-list, \"400\");\n$clr-brown-500: map-get($clr-brown-list, \"500\");\n$clr-brown-600: map-get($clr-brown-list, \"600\");\n$clr-brown-700: map-get($clr-brown-list, \"700\");\n$clr-brown-800: map-get($clr-brown-list, \"800\");\n$clr-brown-900: map-get($clr-brown-list, \"900\");\n\n\n//\n// Grey\n//\n\n$clr-grey-list: (\n  \"base\": #9e9e9e,\n  \"50\":   #fafafa,\n  \"100\":  #f5f5f5,\n  \"200\":  #eeeeee,\n  \"300\":  #e0e0e0,\n  \"400\":  #bdbdbd,\n  \"500\":  #9e9e9e,\n  \"600\":  #757575,\n  \"700\":  #616161,\n  \"800\":  #424242,\n  \"900\":  #212121,\n);\n\n$clr-grey:     map-get($clr-grey-list, \"base\");\n\n$clr-grey-50:  map-get($clr-grey-list, \"50\");\n$clr-grey-100: map-get($clr-grey-list, \"100\");\n$clr-grey-200: map-get($clr-grey-list, \"200\");\n$clr-grey-300: map-get($clr-grey-list, \"300\");\n$clr-grey-400: map-get($clr-grey-list, \"400\");\n$clr-grey-500: map-get($clr-grey-list, \"500\");\n$clr-grey-600: map-get($clr-grey-list, \"600\");\n$clr-grey-700: map-get($clr-grey-list, \"700\");\n$clr-grey-800: map-get($clr-grey-list, \"800\");\n$clr-grey-900: map-get($clr-grey-list, \"900\");\n\n\n//\n// Blue grey\n//\n\n$clr-blue-grey-list: (\n  \"base\": #607d8b,\n  \"50\":   #eceff1,\n  \"100\":  #cfd8dc,\n  \"200\":  #b0bec5,\n  \"300\":  #90a4ae,\n  \"400\":  #78909c,\n  \"500\":  #607d8b,\n  \"600\":  #546e7a,\n  \"700\":  #455a64,\n  \"800\":  #37474f,\n  \"900\":  #263238,\n);\n\n$clr-blue-grey:     map-get($clr-blue-grey-list, \"base\");\n\n$clr-blue-grey-50:  map-get($clr-blue-grey-list, \"50\");\n$clr-blue-grey-100: map-get($clr-blue-grey-list, \"100\");\n$clr-blue-grey-200: map-get($clr-blue-grey-list, \"200\");\n$clr-blue-grey-300: map-get($clr-blue-grey-list, \"300\");\n$clr-blue-grey-400: map-get($clr-blue-grey-list, \"400\");\n$clr-blue-grey-500: map-get($clr-blue-grey-list, \"500\");\n$clr-blue-grey-600: map-get($clr-blue-grey-list, \"600\");\n$clr-blue-grey-700: map-get($clr-blue-grey-list, \"700\");\n$clr-blue-grey-800: map-get($clr-blue-grey-list, \"800\");\n$clr-blue-grey-900: map-get($clr-blue-grey-list, \"900\");\n\n\n//\n// Black\n//\n\n$clr-black-list: (\n  \"base\": #000\n);\n\n$clr-black: map-get($clr-black-list, \"base\");\n\n\n//\n// White\n//\n\n$clr-white-list: (\n  \"base\": #fff\n);\n\n$clr-white: map-get($clr-white-list, \"base\");\n\n\n//\n// List for all Colors for looping\n//\n\n$clr-list-all: (\n  \"red\":         $clr-red-list,\n  \"pink\":        $clr-pink-list,\n  \"purple\":      $clr-purple-list,\n  \"deep-purple\": $clr-deep-purple-list,\n  \"indigo\":      $clr-indigo-list,\n  \"blue\":        $clr-blue-list,\n  \"light-blue\":  $clr-light-blue-list,\n  \"cyan\":        $clr-cyan-list,\n  \"teal\":        $clr-teal-list,\n  \"green\":       $clr-green-list,\n  \"light-green\": $clr-light-green-list,\n  \"lime\":        $clr-lime-list,\n  \"yellow\":      $clr-yellow-list,\n  \"amber\":       $clr-amber-list,\n  \"orange\":      $clr-orange-list,\n  \"deep-orange\": $clr-deep-orange-list,\n  \"brown\":       $clr-brown-list,\n  \"grey\":        $clr-grey-list,\n  \"blue-grey\":   $clr-blue-grey-list,\n  \"black\":       $clr-black-list,\n  \"white\":       $clr-white-list\n);\n\n\n//\n// Typography\n//\n\n$clr-ui-display-4: $clr-grey-600;\n$clr-ui-display-3: $clr-grey-600;\n$clr-ui-display-2: $clr-grey-600;\n$clr-ui-display-1: $clr-grey-600;\n$clr-ui-headline:  $clr-grey-900;\n$clr-ui-title:     $clr-grey-900;\n$clr-ui-subhead-1: $clr-grey-900;\n$clr-ui-body-2:    $clr-grey-900;\n$clr-ui-body-1:    $clr-grey-900;\n$clr-ui-caption:   $clr-grey-600;\n$clr-ui-menu:      $clr-grey-900;\n$clr-ui-button:    $clr-grey-900;\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Footnote reference\n  [id^=\"fnref:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 4px);\n    padding-top: px2rem(48px + 24px - 4px);\n  }\n\n  // Footnote\n  [id^=\"fn:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 3px);\n    padding-top: px2rem(48px + 24px - 3px);\n  }\n\n  // Footnote container\n  .footnote {\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n\n    // Footnote list - omit left indentation\n    ol {\n      margin-left: 0;\n    }\n\n    // Footnote list item\n    li {\n      transition: color 125ms;\n\n      // Darken color on target\n      &:target {\n        color: var(--md-default-fg-color);\n      }\n\n      // Show backreferences on footnote hover\n      &:hover  .footnote-backref,\n      &:target .footnote-backref {\n        transform: translateX(0);\n        opacity: 1;\n      }\n\n      // Adjust spacing on first child\n      > :first-child {\n        margin-top: 0;\n      }\n    }\n  }\n\n  // Footnote backreference\n  .footnote-backref {\n    display: inline-block;\n    color: var(--md-typeset-a-color);\n    // Hack: omit Unicode arrow for replacement with icon\n    font-size: 0;\n    vertical-align: text-bottom;\n    transform: translateX(px2rem(5px));\n    opacity: 0;\n    transition:\n      color     250ms,\n      transform 250ms 250ms,\n      opacity   125ms 250ms;\n\n    // [print]: Show footnote backreferences\n    @media print {\n      color: var(--md-typeset-a-color);\n      transform: translateX(0);\n      opacity: 1;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      transform: translateX(px2rem(-5px));\n    }\n\n    // Adjust color on hover\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Footnote backreference icon\n    &::before {\n      display: inline-block;\n      width: px2rem(16px);\n      height: px2rem(16px);\n      background-color: currentColor;\n      mask-image: var(--md-footnotes-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Headerlink\n  .headerlink {\n    display: inline-block;\n    margin-left: px2rem(10px);\n    color: var(--md-default-fg-color--lighter);\n    opacity: 0;\n    transition:\n      color      250ms,\n      opacity    125ms;\n\n    // [print]: Hide headerlinks\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(10px);\n      margin-left: initial;\n    }\n  }\n\n  // Show headerlinks on parent hover\n  :hover  > .headerlink,\n  :target > .headerlink,\n  .headerlink:focus {\n    opacity: 1;\n    transition:\n      color      250ms,\n      opacity    125ms;\n  }\n\n  // Adjust color on parent target or focus/hover\n  :target > .headerlink,\n  .headerlink:focus,\n  .headerlink:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Adjust scroll offset for all elements with `id` attributes - general scroll\n  // margin offset for anything that can be targeted. Browser support is pretty\n  // decent by now, but Edge <79 and Safari (iOS and macOS) still don't support\n  // it properly, so we settle with a cross-browser anchor correction solution.\n  :target {\n    scroll-margin-top: px2rem(48px + 24px);\n  }\n\n  // Adjust scroll offset for headlines of level 1-3\n  h1:target,\n  h2:target,\n  h3:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 4px);\n      padding-top: px2rem(48px + 24px - 4px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 4\n  h4:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 3px);\n      padding-top: px2rem(48px + 24px - 3px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 5-6\n  h5:target,\n  h6:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px);\n      padding-top: px2rem(48px + 24px);\n      content: \"\";\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Arithmatex container\n  div.arithmatex {\n    overflow: auto;\n\n    // [mobile -]: Align with body copy\n    @include break-to-device(mobile) {\n      margin: 0 px2rem(-16px);\n    }\n\n    // Arithmatex content\n    > * {\n      width: min-content;\n      // stylelint-disable-next-line declaration-no-important\n      margin: 1em auto !important;\n      padding: 0 px2rem(16px);\n      touch-action: auto;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Deletion, addition or comment\n  del.critic,\n  ins.critic,\n  .critic.comment {\n    box-decoration-break: clone;\n  }\n\n  // Deletion\n  del.critic {\n    background-color: var(--md-typeset-del-color);\n  }\n\n  // Addition\n  ins.critic {\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Comment\n  .critic.comment {\n    color: var(--md-code-hl-comment-color);\n\n    // Comment opening mark\n    &::before {\n      content: \"/* \";\n    }\n\n    // Comment closing mark\n    &::after {\n      content: \" */\";\n    }\n  }\n\n  // Critic block\n  .critic.block {\n    display: block;\n    margin: 1em 0;\n    padding-right: px2rem(16px);\n    padding-left: px2rem(16px);\n    overflow: auto;\n    box-shadow: none;\n\n    // Adjust spacing on first child\n    > :first-child {\n      margin-top: 0.5em;\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0.5em;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Details\n  details {\n    @extend .admonition;\n\n    display: flow-root;\n    padding-top: 0;\n    overflow: visible;\n\n    // Details title icon - rotate icon on transition to open state\n    &[open] > summary::after {\n      transform: rotate(90deg);\n    }\n\n    // Adjust spacing for details in closed state\n    &:not([open]) {\n      padding-bottom: 0;\n      box-shadow: none;\n\n      // Hack: we cannot set `overflow: hidden` on the `details` element (which\n      // is why we set it to `overflow: visible`, as the outline would not be\n      // visible when focusing. Therefore, we must set the border radius on the\n      // summary explicitly.\n      > summary {\n        border-radius: px2rem(2px);\n      }\n    }\n\n    // Hack: omit margin collapse\n    &::after {\n      display: table;\n      content: \"\";\n    }\n  }\n\n  // Details title\n  summary {\n    @extend .admonition-title;\n\n    display: block;\n    min-height: px2rem(20px);\n    padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);\n    border-top-left-radius: px2rem(2px);\n    border-top-right-radius: px2rem(2px);\n    cursor: pointer;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: px2rem(8px) px2rem(44px) px2rem(8px) px2rem(36px);\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Details marker\n    &::after {\n      position: absolute;\n      top: px2rem(8px);\n      right: px2rem(8px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: currentColor;\n      mask-image: var(--md-details-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      transform: rotate(0deg);\n      transition: transform 250ms;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(8px);\n        transform: rotate(180deg);\n      }\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Emoji and icon container\n  .emojione,\n  .twemoji,\n  .gemoji {\n    display: inline-flex;\n    height: px2em(18px);\n    vertical-align: text-top;\n\n    // Icon - inlined via mkdocs-material-extensions\n    svg {\n      width: px2em(18px);\n      max-height: 100%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: syntax highlighting\n// ----------------------------------------------------------------------------\n\n// Code block\n.highlight {\n  .o,   // Operator\n  .ow { // Operator, word\n    color: var(--md-code-hl-operator-color);\n  }\n\n  .p {  // Punctuation\n    color: var(--md-code-hl-punctuation-color);\n  }\n\n  .cpf, // Comment, preprocessor file\n  .l,   // Literal\n  .s,   // Literal, string\n  .sb,  // Literal, string backticks\n  .sc,  // Literal, string char\n  .s2,  // Literal, string double\n  .si,  // Literal, string interpol\n  .s1,  // Literal, string single\n  .ss { // Literal, string symbol\n    color: var(--md-code-hl-string-color);\n  }\n\n  .cp,  // Comment, pre-processor\n  .se,  // Literal, string escape\n  .sh,  // Literal, string heredoc\n  .sr,  // Literal, string regex\n  .sx { // Literal, string other\n    color: var(--md-code-hl-special-color);\n  }\n\n  .m,   // Number\n  .mb,  // Number, binary\n  .mf,  // Number, float\n  .mh,  // Number, hex\n  .mi,  // Number, integer\n  .il,  // Number, integer long\n  .mo { // Number, octal\n    color: var(--md-code-hl-number-color);\n  }\n\n  .k,   // Keyword,\n  .kd,  // Keyword, declaration\n  .kn,  // Keyword, namespace\n  .kp,  // Keyword, pseudo\n  .kr,  // Keyword, reserved\n  .kt { // Keyword, type\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .kc,  // Keyword, constant\n  .n {  // Name\n    color: var(--md-code-hl-name-color);\n  }\n\n  .no,  // Name, constant\n  .nb,  // Name, builtin\n  .bp { // Name, builtin pseudo\n    color: var(--md-code-hl-constant-color);\n  }\n\n  .nc,  // Name, class\n  .ne,  // Name, exception\n  .nf,  // Name, function\n  .nn { // Name, namespace\n    color: var(--md-code-hl-function-color);\n  }\n\n  .nd,  // Name, decorator\n  .ni,  // Name, entity\n  .nl,  // Name, label\n  .nt { // Name, tag\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .c,   // Comment\n  .cm,  // Comment, multiline\n  .c1,  // Comment, single\n  .ch,  // Comment, shebang\n  .cs,  // Comment, special\n  .sd { // Literal, string doc\n    color: var(--md-code-hl-comment-color);\n  }\n\n  .na,  // Name, attribute\n  .nv,  // Variable,\n  .vc,  // Variable, class\n  .vg,  // Variable, global\n  .vi { // Variable, instance\n    color: var(--md-code-hl-variable-color);\n  }\n\n  .ge,  // Generic, emph\n  .gr,  // Generic, error\n  .gh,  // Generic, heading\n  .go,  // Generic, output\n  .gp,  // Generic, prompt\n  .gs,  // Generic, strong\n  .gu,  // Generic, subheading\n  .gt { // Generic, traceback\n    color: var(--md-code-hl-generic-color);\n  }\n\n  .gd,  // Diff, delete\n  .gi { // Diff, insert\n    margin: 0 px2em(-2px);\n    padding: 0 px2em(2px);\n    border-radius: px2rem(2px);\n  }\n\n  .gd { // Diff, delete\n    background-color: var(--md-typeset-del-color);\n  }\n\n  .gi { // Diff, insert\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Highlighted line\n  .hll {\n    display: block;\n    margin: 0 px2em(-16px, 13.6px);\n    padding: 0 px2em(16px, 13.6px);\n    background-color: var(--md-code-hl-color);\n  }\n\n  // Code block line numbers (inline)\n  [data-linenos]::before {\n    position: sticky;\n    left: px2em(-16px, 13.6px);\n    float: left;\n    margin-right: px2em(16px, 13.6px);\n    margin-left: px2em(-16px, 13.6px);\n    padding-left: px2em(16px, 13.6px);\n    color: var(--md-default-fg-color--light);\n    background-color: var(--md-code-bg-color);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n    content: attr(data-linenos);\n    user-select: none;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Code block with line numbers\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n\n  // Set table elements to block layout, because otherwise the whole flexbox\n  // hacking won't work correctly\n  tbody,\n  td {\n    display: block;\n    padding: 0;\n  }\n\n  // We need to use flexbox layout, because otherwise it's not possible to\n  // make the code container scroll while keeping the line numbers static\n  tr {\n    display: flex;\n  }\n\n  // The pre tags are nested inside a table, so we need to omit the margin\n  // because it collapses below all the overflows\n  pre {\n    margin: 0;\n  }\n\n  // Code block line numbers - disable user selection, so code can be easily\n  // copied without accidentally also copying the line numbers\n  .linenos {\n    padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n    padding-right: 0;\n    font-size: px2em(13.6px);\n    background-color: var(--md-code-bg-color);\n    user-select: none;\n  }\n\n  // Code block line numbers container\n  .linenodiv {\n    padding-right: px2em(8px, 13.6px);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n\n    // Adjust colors and alignment\n    pre {\n      color: var(--md-default-fg-color--light);\n      text-align: right;\n    }\n  }\n\n  // Code block container - stretch to remaining space\n  .code {\n    flex: 1;\n    overflow: hidden;\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Code block with line numbers\n  .highlighttable {\n    margin: 1em 0;\n    direction: ltr;\n    border-radius: px2rem(2px);\n\n    // Omit rounded borders on contained code block\n    code {\n      border-radius: 0;\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Top-level code block\n    > .highlight {\n      margin: 1em px2rem(-16px);\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n\n      // Omit rounded borders\n      code {\n        border-radius: 0;\n      }\n    }\n\n    // Top-level code block with line numbers\n    > .highlighttable {\n      margin: 1em px2rem(-16px);\n      border-radius: 0;\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Keyboard key\n  .keys {\n\n    // Keyboard key icon\n    kbd::before,\n    kbd::after {\n      position: relative;\n      margin: 0;\n      color: inherit;\n      -moz-osx-font-smoothing: initial;\n      -webkit-font-smoothing: initial;\n    }\n\n    // Surrounding text\n    span {\n      padding: 0 px2em(3.2px);\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Define keyboard keys with left icon\n    @each $name, $code in (\n\n      // Modifiers\n      \"alt\":           \"\\2387\",\n      \"left-alt\":      \"\\2387\",\n      \"right-alt\":     \"\\2387\",\n      \"command\":       \"\\2318\",\n      \"left-command\":  \"\\2318\",\n      \"right-command\": \"\\2318\",\n      \"control\":       \"\\2303\",\n      \"left-control\":  \"\\2303\",\n      \"right-control\": \"\\2303\",\n      \"meta\":          \"\\25C6\",\n      \"left-meta\":     \"\\25C6\",\n      \"right-meta\":    \"\\25C6\",\n      \"option\":        \"\\2325\",\n      \"left-option\":   \"\\2325\",\n      \"right-option\":  \"\\2325\",\n      \"shift\":         \"\\21E7\",\n      \"left-shift\":    \"\\21E7\",\n      \"right-shift\":   \"\\21E7\",\n      \"super\":         \"\\2756\",\n      \"left-super\":    \"\\2756\",\n      \"right-super\":   \"\\2756\",\n      \"windows\":       \"\\229E\",\n      \"left-windows\":  \"\\229E\",\n      \"right-windows\": \"\\229E\",\n\n      // Other keys\n      \"arrow-down\":    \"\\2193\",\n      \"arrow-left\":    \"\\2190\",\n      \"arrow-right\":   \"\\2192\",\n      \"arrow-up\":      \"\\2191\",\n      \"backspace\":     \"\\232B\",\n      \"backtab\":       \"\\21E4\",\n      \"caps-lock\":     \"\\21EA\",\n      \"clear\":         \"\\2327\",\n      \"context-menu\":  \"\\2630\",\n      \"delete\":        \"\\2326\",\n      \"eject\":         \"\\23CF\",\n      \"end\":           \"\\2913\",\n      \"escape\":        \"\\238B\",\n      \"home\":          \"\\2912\",\n      \"insert\":        \"\\2380\",\n      \"page-down\":     \"\\21DF\",\n      \"page-up\":       \"\\21DE\",\n      \"print-screen\":  \"\\2399\"\n    ) {\n      .key-#{$name} {\n        &::before {\n          padding-right: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n\n    // Define keyboard keys with right icon\n    @each $name, $code in (\n      \"tab\":           \"\\21E5\",\n      \"num-enter\":     \"\\2324\",\n      \"enter\":         \"\\23CE\"\n    ) {\n      .key-#{$name} {\n        &::after {\n          padding-left: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tabbed block content\n  .tabbed-content {\n    display: none;\n    order: 99;\n    width: 100%;\n    box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);\n\n    // [print]: Show all tabs (even hidden ones) when printing\n    @media print {\n      display: block;\n      order: initial;\n    }\n\n    // Code block is the only child of a tab - remove margin and mirror\n    // previous (now deprecated) SuperFences code block grouping behavior\n    > pre:only-child,\n    > .highlight:only-child pre,\n    > .highlighttable:only-child {\n      margin: 0;\n\n      // Omit rounded borders\n      > code {\n        border-top-left-radius: 0;\n        border-top-right-radius: 0;\n      }\n    }\n\n    // Adjust spacing for nested tab\n    > .tabbed-set {\n      margin: 0;\n    }\n  }\n\n  // Tabbed block container\n  .tabbed-set {\n    position: relative;\n    display: flex;\n    flex-wrap: wrap;\n    margin: 1em 0;\n    border-radius: px2rem(2px);\n\n    // Tab radio button - the Tabbed extension will generate radio buttons with\n    // labels, so tabs can be triggered without the necessity for JavaScript.\n    // This is pretty cool, as it has great accessibility out-of-the box, so\n    // we just hide the radio button and toggle the label color for indication.\n    > input {\n      position: absolute;\n      width: 0;\n      height: 0;\n      opacity: 0;\n\n      // Tab label for checked radio button\n      &:checked + label {\n        color: var(--md-accent-fg-color);\n        border-color: var(--md-accent-fg-color);\n\n        // Show tabbed block content\n        + .tabbed-content {\n          display: block;\n        }\n      }\n\n      // Tab label on focus\n      &:focus + label {\n        outline-style: auto;\n      }\n\n      // Hide outline for pointer devices\n      &:not(.focus-visible) + label {\n        outline: none;\n        -webkit-tap-highlight-color: transparent;\n      }\n    }\n\n    // Tab label\n    > label {\n      z-index: 1;\n      width: auto;\n      padding: px2em(12px, 12.8px) 1.25em px2em(10px, 12.8px);\n      color: var(--md-default-fg-color--light);\n      font-weight: 700;\n      font-size: px2rem(12.8px);\n      border-bottom: px2rem(2px) solid transparent;\n      cursor: pointer;\n      transition: color 250ms;\n\n      // Tab label on hover\n      &:hover {\n        color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tasklist item\n  .task-list-item {\n    position: relative;\n    list-style-type: none;\n\n    // Make checkbox items align with normal list items, but position\n    // everything in ems for correct layout at smaller font sizes\n    [type=\"checkbox\"] {\n      position: absolute;\n      top: 0.45em;\n      left: -2em;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: -2em;\n        left: initial;\n      }\n    }\n  }\n\n  // Hide native checkbox, when custom classes are enabled\n  .task-list-control [type=\"checkbox\"] {\n    z-index: -1;\n    opacity: 0;\n  }\n\n  // Tasklist indicator in unchecked state\n  .task-list-indicator::before {\n    position: absolute;\n    top: 0.15em;\n    left: px2em(-24px);\n    width: px2em(20px);\n    height: px2em(20px);\n    background-color: var(--md-default-fg-color--lightest);\n    mask-image: var(--md-tasklist-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: px2em(-24px);\n      left: initial;\n    }\n  }\n\n  // Tasklist indicator in checked state\n  [type=\"checkbox\"]:checked + .task-list-indicator::before {\n    background-color: $clr-green-a400;\n    mask-image: var(--md-tasklist-icon--checked);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // [tablet +]: Allow for rendering content as sidebars\n  @include break-from-device(tablet) {\n\n    // Modifier to float block elements\n    .inline {\n      float: left;\n      width: px2rem(234px);\n      margin-top: 0;\n      margin-right: px2rem(16px);\n      margin-bottom: px2rem(16px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n      }\n\n      // Modifier to move to end (ltr: right, rtl: left)\n      &.end {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          float: left;\n          margin-right: px2rem(16px);\n          margin-left: 0;\n        }\n      }\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/palette.7fa14f5b.min.css b/5.4/assets/stylesheets/palette.7fa14f5b.min.css
deleted file mode 100644 (file)
index 1d46bb4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-[data-md-color-accent=red]{--md-accent-fg-color:#ff1a47;--md-accent-fg-color--transparent:rgba(255,26,71,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,136,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,0.7);--md-primary-fg-color--dark:rgba(0,0,0,0.07);--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:hover{background-color:rgba(0,0,0,.32)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,0.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__input:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}}@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,136,255,0.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-typeset-a-color:var(--md-primary-fg-color--light);--md-typeset-mark-color:rgba(66,136,255,0.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-admonition-bg-color:hsla(var(--md-hue),0%,100%,0.025);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1)}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5d6cc0}}
-/*# sourceMappingURL=palette.7fa14f5b.min.css.map */
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/palette.7fa14f5b.min.css.map b/5.4/assets/stylesheets/palette.7fa14f5b.min.css.map
deleted file mode 100644 (file)
index 62a9e04..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/palette/_scheme.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/palette.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $color in (\n  \"red\":         $clr-red-a400,\n  \"pink\":        $clr-pink-a400,\n  \"purple\":      $clr-purple-a200,\n  \"deep-purple\": $clr-deep-purple-a200,\n  \"indigo\":      $clr-indigo-a200,\n  \"blue\":        $clr-blue-a200,\n  \"light-blue\":  $clr-light-blue-a700,\n  \"cyan\":        $clr-cyan-a700,\n  \"teal\":        $clr-teal-a700,\n  \"green\":       $clr-green-a700,\n  \"light-green\": $clr-light-green-a700,\n  \"lime\":        $clr-lime-a700,\n  \"yellow\":      $clr-yellow-a700,\n  \"amber\":       $clr-amber-a700,\n  \"orange\":      $clr-orange-a400,\n  \"deep-orange\": $clr-deep-orange-a200\n) {\n\n  // Color palette\n  [data-md-color-accent=\"#{$name}\"] {\n    --md-accent-fg-color:              hsla(#{hex2hsl($color)}, 1);\n    --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-accent-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-accent-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-accent-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-accent-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n","[data-md-color-accent=red] {\n  --md-accent-fg-color: hsla(348, 100%, 55%, 1);\n  --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=pink] {\n  --md-accent-fg-color: hsla(339, 100%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(339, 100%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=purple] {\n  --md-accent-fg-color: hsla(291, 96%, 62%, 1);\n  --md-accent-fg-color--transparent: hsla(291, 96%, 62%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=deep-purple] {\n  --md-accent-fg-color: hsla(256, 100%, 65%, 1);\n  --md-accent-fg-color--transparent: hsla(256, 100%, 65%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=indigo] {\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=blue] {\n  --md-accent-fg-color: hsla(218, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(218, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-blue] {\n  --md-accent-fg-color: hsla(203, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(203, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=cyan] {\n  --md-accent-fg-color: hsla(188, 100%, 42%, 1);\n  --md-accent-fg-color--transparent: hsla(188, 100%, 42%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=teal] {\n  --md-accent-fg-color: hsla(172, 100%, 37%, 1);\n  --md-accent-fg-color--transparent: hsla(172, 100%, 37%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=green] {\n  --md-accent-fg-color: hsla(145, 100%, 39%, 1);\n  --md-accent-fg-color--transparent: hsla(145, 100%, 39%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-green] {\n  --md-accent-fg-color: hsla(97, 81%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(97, 81%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=lime] {\n  --md-accent-fg-color: hsla(75, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(75, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=yellow] {\n  --md-accent-fg-color: hsla(50, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(50, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=amber] {\n  --md-accent-fg-color: hsla(40, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(40, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=orange] {\n  --md-accent-fg-color: hsla(34, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(34, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=deep-orange] {\n  --md-accent-fg-color: hsla(14, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(14, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=red] {\n  --md-primary-fg-color: hsla(1, 83%, 63%, 1);\n  --md-primary-fg-color--light: hsla(0, 69%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(1, 77%, 55%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=pink] {\n  --md-primary-fg-color: hsla(340, 82%, 52%, 1);\n  --md-primary-fg-color--light: hsla(340, 82%, 59%, 1);\n  --md-primary-fg-color--dark: hsla(336, 78%, 43%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=purple] {\n  --md-primary-fg-color: hsla(291, 47%, 51%, 1);\n  --md-primary-fg-color--light: hsla(291, 47%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(287, 65%, 40%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=deep-purple] {\n  --md-primary-fg-color: hsla(262, 47%, 55%, 1);\n  --md-primary-fg-color--light: hsla(262, 47%, 63%, 1);\n  --md-primary-fg-color--dark: hsla(262, 52%, 47%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=indigo] {\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue] {\n  --md-primary-fg-color: hsla(207, 90%, 54%, 1);\n  --md-primary-fg-color--light: hsla(207, 90%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(210, 79%, 46%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-blue] {\n  --md-primary-fg-color: hsla(199, 98%, 48%, 1);\n  --md-primary-fg-color--light: hsla(199, 92%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(201, 98%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=cyan] {\n  --md-primary-fg-color: hsla(187, 100%, 42%, 1);\n  --md-primary-fg-color--light: hsla(187, 71%, 50%, 1);\n  --md-primary-fg-color--dark: hsla(186, 100%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=teal] {\n  --md-primary-fg-color: hsla(174, 100%, 29%, 1);\n  --md-primary-fg-color--light: hsla(174, 63%, 40%, 1);\n  --md-primary-fg-color--dark: hsla(173, 100%, 24%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=green] {\n  --md-primary-fg-color: hsla(122, 39%, 49%, 1);\n  --md-primary-fg-color--light: hsla(123, 38%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(123, 43%, 39%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-green] {\n  --md-primary-fg-color: hsla(88, 50%, 53%, 1);\n  --md-primary-fg-color--light: hsla(88, 50%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(92, 48%, 42%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=lime] {\n  --md-primary-fg-color: hsla(66, 70%, 54%, 1);\n  --md-primary-fg-color--light: hsla(66, 70%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(62, 61%, 44%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=yellow] {\n  --md-primary-fg-color: hsla(54, 100%, 62%, 1);\n  --md-primary-fg-color--light: hsla(54, 100%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(43, 96%, 58%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=amber] {\n  --md-primary-fg-color: hsla(45, 100%, 51%, 1);\n  --md-primary-fg-color--light: hsla(45, 100%, 58%, 1);\n  --md-primary-fg-color--dark: hsla(38, 100%, 50%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=orange] {\n  --md-primary-fg-color: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--light: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(33, 100%, 49%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=deep-orange] {\n  --md-primary-fg-color: hsla(14, 100%, 63%, 1);\n  --md-primary-fg-color--light: hsla(14, 100%, 70%, 1);\n  --md-primary-fg-color--dark: hsla(14, 91%, 54%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=brown] {\n  --md-primary-fg-color: hsla(16, 25%, 38%, 1);\n  --md-primary-fg-color--light: hsla(16, 18%, 47%, 1);\n  --md-primary-fg-color--dark: hsla(14, 26%, 29%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=grey] {\n  --md-primary-fg-color: hsla(0, 0%, 46%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 62%, 1);\n  --md-primary-fg-color--dark: hsla(0, 0%, 38%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue-grey] {\n  --md-primary-fg-color: hsla(199, 18%, 40%, 1);\n  --md-primary-fg-color--light: hsla(200, 18%, 46%, 1);\n  --md-primary-fg-color--dark: hsla(199, 18%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=white] {\n  --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=white] .md-search__input {\n    background-color: rgba(0, 0, 0, 0.07);\n  }\n  [data-md-color-primary=white] .md-search__input + .md-search__icon {\n    color: rgba(0, 0, 0, 0.87);\n  }\n  [data-md-color-primary=white] .md-search__input::placeholder {\n    color: rgba(0, 0, 0, 0.54);\n  }\n  [data-md-color-primary=white] .md-search__input:hover {\n    background-color: rgba(0, 0, 0, 0.32);\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=white] .md-tabs {\n    border-bottom: 0.05rem solid rgba(0, 0, 0, 0.07);\n  }\n}\n\n[data-md-color-primary=black] {\n  --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n[data-md-color-primary=black] .md-header {\n  background-color: black;\n}\n@media screen and (max-width: 59.9375em) {\n  [data-md-color-primary=black] .md-nav__source {\n    background-color: rgba(0, 0, 0, 0.87);\n  }\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=black] .md-search__input {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-color-primary=black] .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.3);\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer] {\n    background-color: black;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=black] .md-tabs {\n    background-color: black;\n  }\n}\n\n@media screen {\n  [data-md-color-scheme=slate] {\n    --md-hue: 232;\n    --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n    --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n    --md-code-hl-color: hsla(218, 100%, 63%, 0.15);\n    --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n    --md-typeset-a-color: var(--md-primary-fg-color--light);\n    --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3);\n    --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n    --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n    --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n  }\n  [data-md-color-scheme=slate][data-md-color-primary=black], [data-md-color-scheme=slate][data-md-color-primary=white] {\n    --md-typeset-a-color: hsla(231, 44%, 56%, 1);\n  }\n}\n\n/*# sourceMappingURL=palette.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $colors in (\n  \"red\":         $clr-red-400         $clr-red-300         $clr-red-600,\n  \"pink\":        $clr-pink-500        $clr-pink-400        $clr-pink-700,\n  \"purple\":      $clr-purple-400      $clr-purple-300      $clr-purple-600,\n  \"deep-purple\": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,\n  \"indigo\":      $clr-indigo-500      $clr-indigo-400      $clr-indigo-700,\n  \"blue\":        $clr-blue-500        $clr-blue-400        $clr-blue-700,\n  \"light-blue\":  $clr-light-blue-500  $clr-light-blue-400  $clr-light-blue-700,\n  \"cyan\":        $clr-cyan-500        $clr-cyan-400        $clr-cyan-700,\n  \"teal\":        $clr-teal-500        $clr-teal-400        $clr-teal-700,\n  \"green\":       $clr-green-500       $clr-green-400       $clr-green-700,\n  \"light-green\": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,\n  \"lime\":        $clr-lime-500        $clr-lime-400        $clr-lime-700,\n  \"yellow\":      $clr-yellow-500      $clr-yellow-400      $clr-yellow-700,\n  \"amber\":       $clr-amber-500       $clr-amber-400       $clr-amber-700,\n  \"orange\":      $clr-orange-400      $clr-orange-400      $clr-orange-600,\n  \"deep-orange\": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,\n  \"brown\":       $clr-brown-500       $clr-brown-400       $clr-brown-700,\n  \"grey\":        $clr-grey-600        $clr-grey-500        $clr-grey-700,\n  \"blue-grey\":   $clr-blue-grey-600   $clr-blue-grey-500   $clr-blue-grey-700\n) {\n\n  // Color palette\n  [data-md-color-primary=\"#{$name}\"] {\n    --md-primary-fg-color:             hsla(#{hex2hsl(nth($colors, 1))}, 1);\n    --md-primary-fg-color--light:      hsla(#{hex2hsl(nth($colors, 2))}, 1);\n    --md-primary-fg-color--dark:       hsla(#{hex2hsl(nth($colors, 3))}, 1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-primary-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-primary-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-primary-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-primary-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: white\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"white\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light:        hsla(0, 0%, 0%, 0.54);\n\n  // Typeset color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // [tablet portrait +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 0%, 0.07);\n\n      // Search icon color\n      + .md-search__icon {\n        color: hsla(0, 0%, 0%, 0.87);\n      }\n\n      // Placeholder color\n      &::placeholder {\n        color: hsla(0, 0%, 0%, 0.54);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 0%, 0.32);\n      }\n    }\n  }\n\n  // [screen +]: Add bottom border for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: black\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"black\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Text color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // Header\n  .md-header {\n    background-color: hsla(0, 0%, 0%, 1);\n  }\n\n  // [tablet portrait -]: Layered navigation\n  @include break-to-device(tablet portrait) {\n\n    // Repository information container\n    .md-nav__source {\n      background-color: hsla(0, 0%, 0%, 0.87);\n    }\n  }\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 100%, 0.12);\n\n      // Search form on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.3);\n      }\n    }\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Site title in main navigation\n    html & .md-nav--primary .md-nav__title[for=\"__drawer\"] {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n\n  // [screen +]: Set background color for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Only use dark mode on screens\n@media screen {\n\n  // Slate theme, i.e. dark mode\n  [data-md-color-scheme=\"slate\"] {\n\n    // Slate's hue in the range [0,360] - change this variable to alter the tone\n    // of the theme, e.g. to make it more redish or greenish. This is a slate-\n    // specific variable, but the same approach may be adapted to custom themes.\n    --md-hue: 232;\n\n    // Default color shades\n    --md-default-fg-color:             hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light:      hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter:    hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest:   hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color:             hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light:      hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter:    hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest:   hsla(var(--md-hue), 15%, 21%, 0.07);\n\n    // Code color shades\n    --md-code-fg-color:                hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color:                hsla(var(--md-hue), 15%, 15%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-blue-a200)}, 0.15);\n    --md-code-hl-number-color:         hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color:       hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color:       hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color:         hsla(150, 58%, 44%, 1);\n\n    // Typeset color shades\n    --md-typeset-a-color:              var(--md-primary-fg-color--light);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-blue-a200)}, 0.3);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color:     hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color:     hsla(var(--md-hue), 15%, 14%, 1);\n\n    // Admonition color shades\n    --md-admonition-bg-color:          hsla(var(--md-hue), 0%, 100%, 0.025);\n\n    // Footer color shades\n    --md-footer-bg-color:              hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark:        hsla(var(--md-hue), 15%, 10%, 1);\n\n    // Black and white primary colors\n    &[data-md-color-primary=\"black\"],\n    &[data-md-color-primary=\"white\"] {\n\n      // Typeset color shades\n      --md-typeset-a-color:            hsla(#{hex2hsl($clr-indigo-400)}, 1);\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/palette.ef6f36e2.min.css b/5.4/assets/stylesheets/palette.ef6f36e2.min.css
new file mode 100644 (file)
index 0000000..e0711bd
--- /dev/null
@@ -0,0 +1,2 @@
+[data-md-color-accent=red]{--md-accent-fg-color:#ff1a47;--md-accent-fg-color--transparent:rgba(255,26,71,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,136,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,0.7);--md-primary-fg-color--dark:rgba(0,0,0,0.07);--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:hover{background-color:rgba(0,0,0,.32)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,0.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__input:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}}@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,136,255,0.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(66,136,255,0.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-admonition-bg-color:hsla(var(--md-hue),0%,100%,0.025);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1)}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5d6cc0}}
+/*# sourceMappingURL=palette.ef6f36e2.min.css.map */
\ No newline at end of file
diff --git a/5.4/assets/stylesheets/palette.ef6f36e2.min.css.map b/5.4/assets/stylesheets/palette.ef6f36e2.min.css.map
new file mode 100644 (file)
index 0000000..da0a67b
--- /dev/null
@@ -0,0 +1 @@
+{"version":3,"sources":["src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/palette/_scheme.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/palette.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $color in (\n  \"red\":         $clr-red-a400,\n  \"pink\":        $clr-pink-a400,\n  \"purple\":      $clr-purple-a200,\n  \"deep-purple\": $clr-deep-purple-a200,\n  \"indigo\":      $clr-indigo-a200,\n  \"blue\":        $clr-blue-a200,\n  \"light-blue\":  $clr-light-blue-a700,\n  \"cyan\":        $clr-cyan-a700,\n  \"teal\":        $clr-teal-a700,\n  \"green\":       $clr-green-a700,\n  \"light-green\": $clr-light-green-a700,\n  \"lime\":        $clr-lime-a700,\n  \"yellow\":      $clr-yellow-a700,\n  \"amber\":       $clr-amber-a700,\n  \"orange\":      $clr-orange-a400,\n  \"deep-orange\": $clr-deep-orange-a200\n) {\n\n  // Color palette\n  [data-md-color-accent=\"#{$name}\"] {\n    --md-accent-fg-color:              hsla(#{hex2hsl($color)}, 1);\n    --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-accent-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-accent-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-accent-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-accent-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n","[data-md-color-accent=red] {\n  --md-accent-fg-color: hsla(348, 100%, 55%, 1);\n  --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=pink] {\n  --md-accent-fg-color: hsla(339, 100%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(339, 100%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=purple] {\n  --md-accent-fg-color: hsla(291, 96%, 62%, 1);\n  --md-accent-fg-color--transparent: hsla(291, 96%, 62%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=deep-purple] {\n  --md-accent-fg-color: hsla(256, 100%, 65%, 1);\n  --md-accent-fg-color--transparent: hsla(256, 100%, 65%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=indigo] {\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=blue] {\n  --md-accent-fg-color: hsla(218, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(218, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-blue] {\n  --md-accent-fg-color: hsla(203, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(203, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=cyan] {\n  --md-accent-fg-color: hsla(188, 100%, 42%, 1);\n  --md-accent-fg-color--transparent: hsla(188, 100%, 42%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=teal] {\n  --md-accent-fg-color: hsla(172, 100%, 37%, 1);\n  --md-accent-fg-color--transparent: hsla(172, 100%, 37%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=green] {\n  --md-accent-fg-color: hsla(145, 100%, 39%, 1);\n  --md-accent-fg-color--transparent: hsla(145, 100%, 39%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-green] {\n  --md-accent-fg-color: hsla(97, 81%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(97, 81%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=lime] {\n  --md-accent-fg-color: hsla(75, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(75, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=yellow] {\n  --md-accent-fg-color: hsla(50, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(50, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=amber] {\n  --md-accent-fg-color: hsla(40, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(40, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=orange] {\n  --md-accent-fg-color: hsla(34, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(34, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=deep-orange] {\n  --md-accent-fg-color: hsla(14, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(14, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=red] {\n  --md-primary-fg-color: hsla(1, 83%, 63%, 1);\n  --md-primary-fg-color--light: hsla(0, 69%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(1, 77%, 55%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=pink] {\n  --md-primary-fg-color: hsla(340, 82%, 52%, 1);\n  --md-primary-fg-color--light: hsla(340, 82%, 59%, 1);\n  --md-primary-fg-color--dark: hsla(336, 78%, 43%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=purple] {\n  --md-primary-fg-color: hsla(291, 47%, 51%, 1);\n  --md-primary-fg-color--light: hsla(291, 47%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(287, 65%, 40%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=deep-purple] {\n  --md-primary-fg-color: hsla(262, 47%, 55%, 1);\n  --md-primary-fg-color--light: hsla(262, 47%, 63%, 1);\n  --md-primary-fg-color--dark: hsla(262, 52%, 47%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=indigo] {\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue] {\n  --md-primary-fg-color: hsla(207, 90%, 54%, 1);\n  --md-primary-fg-color--light: hsla(207, 90%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(210, 79%, 46%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-blue] {\n  --md-primary-fg-color: hsla(199, 98%, 48%, 1);\n  --md-primary-fg-color--light: hsla(199, 92%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(201, 98%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=cyan] {\n  --md-primary-fg-color: hsla(187, 100%, 42%, 1);\n  --md-primary-fg-color--light: hsla(187, 71%, 50%, 1);\n  --md-primary-fg-color--dark: hsla(186, 100%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=teal] {\n  --md-primary-fg-color: hsla(174, 100%, 29%, 1);\n  --md-primary-fg-color--light: hsla(174, 63%, 40%, 1);\n  --md-primary-fg-color--dark: hsla(173, 100%, 24%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=green] {\n  --md-primary-fg-color: hsla(122, 39%, 49%, 1);\n  --md-primary-fg-color--light: hsla(123, 38%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(123, 43%, 39%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-green] {\n  --md-primary-fg-color: hsla(88, 50%, 53%, 1);\n  --md-primary-fg-color--light: hsla(88, 50%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(92, 48%, 42%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=lime] {\n  --md-primary-fg-color: hsla(66, 70%, 54%, 1);\n  --md-primary-fg-color--light: hsla(66, 70%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(62, 61%, 44%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=yellow] {\n  --md-primary-fg-color: hsla(54, 100%, 62%, 1);\n  --md-primary-fg-color--light: hsla(54, 100%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(43, 96%, 58%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=amber] {\n  --md-primary-fg-color: hsla(45, 100%, 51%, 1);\n  --md-primary-fg-color--light: hsla(45, 100%, 58%, 1);\n  --md-primary-fg-color--dark: hsla(38, 100%, 50%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=orange] {\n  --md-primary-fg-color: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--light: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(33, 100%, 49%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=deep-orange] {\n  --md-primary-fg-color: hsla(14, 100%, 63%, 1);\n  --md-primary-fg-color--light: hsla(14, 100%, 70%, 1);\n  --md-primary-fg-color--dark: hsla(14, 91%, 54%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=brown] {\n  --md-primary-fg-color: hsla(16, 25%, 38%, 1);\n  --md-primary-fg-color--light: hsla(16, 18%, 47%, 1);\n  --md-primary-fg-color--dark: hsla(14, 26%, 29%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=grey] {\n  --md-primary-fg-color: hsla(0, 0%, 46%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 62%, 1);\n  --md-primary-fg-color--dark: hsla(0, 0%, 38%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue-grey] {\n  --md-primary-fg-color: hsla(199, 18%, 40%, 1);\n  --md-primary-fg-color--light: hsla(200, 18%, 46%, 1);\n  --md-primary-fg-color--dark: hsla(199, 18%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=white] {\n  --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=white] .md-search__input {\n    background-color: rgba(0, 0, 0, 0.07);\n  }\n  [data-md-color-primary=white] .md-search__input + .md-search__icon {\n    color: rgba(0, 0, 0, 0.87);\n  }\n  [data-md-color-primary=white] .md-search__input::placeholder {\n    color: rgba(0, 0, 0, 0.54);\n  }\n  [data-md-color-primary=white] .md-search__input:hover {\n    background-color: rgba(0, 0, 0, 0.32);\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=white] .md-tabs {\n    border-bottom: 0.05rem solid rgba(0, 0, 0, 0.07);\n  }\n}\n\n[data-md-color-primary=black] {\n  --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n[data-md-color-primary=black] .md-header {\n  background-color: black;\n}\n@media screen and (max-width: 59.9375em) {\n  [data-md-color-primary=black] .md-nav__source {\n    background-color: rgba(0, 0, 0, 0.87);\n  }\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=black] .md-search__input {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-color-primary=black] .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.3);\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer] {\n    background-color: black;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=black] .md-tabs {\n    background-color: black;\n  }\n}\n\n@media screen {\n  [data-md-color-scheme=slate] {\n    --md-hue: 232;\n    --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n    --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n    --md-code-hl-color: hsla(218, 100%, 63%, 0.15);\n    --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n    --md-code-hl-name-color: var(--md-code-fg-color);\n    --md-code-hl-operator-color: var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n    --md-code-hl-comment-color: var(--md-default-fg-color--light);\n    --md-code-hl-generic-color: var(--md-default-fg-color--light);\n    --md-code-hl-variable-color: var(--md-default-fg-color--light);\n    --md-typeset-color: var(--md-default-fg-color);\n    --md-typeset-a-color: var(--md-primary-fg-color);\n    --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3);\n    --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n    --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n    --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n  }\n  [data-md-color-scheme=slate][data-md-color-primary=black], [data-md-color-scheme=slate][data-md-color-primary=white] {\n    --md-typeset-a-color: hsla(231, 44%, 56%, 1);\n  }\n}\n\n/*# sourceMappingURL=palette.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $colors in (\n  \"red\":         $clr-red-400         $clr-red-300         $clr-red-600,\n  \"pink\":        $clr-pink-500        $clr-pink-400        $clr-pink-700,\n  \"purple\":      $clr-purple-400      $clr-purple-300      $clr-purple-600,\n  \"deep-purple\": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,\n  \"indigo\":      $clr-indigo-500      $clr-indigo-400      $clr-indigo-700,\n  \"blue\":        $clr-blue-500        $clr-blue-400        $clr-blue-700,\n  \"light-blue\":  $clr-light-blue-500  $clr-light-blue-400  $clr-light-blue-700,\n  \"cyan\":        $clr-cyan-500        $clr-cyan-400        $clr-cyan-700,\n  \"teal\":        $clr-teal-500        $clr-teal-400        $clr-teal-700,\n  \"green\":       $clr-green-500       $clr-green-400       $clr-green-700,\n  \"light-green\": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,\n  \"lime\":        $clr-lime-500        $clr-lime-400        $clr-lime-700,\n  \"yellow\":      $clr-yellow-500      $clr-yellow-400      $clr-yellow-700,\n  \"amber\":       $clr-amber-500       $clr-amber-400       $clr-amber-700,\n  \"orange\":      $clr-orange-400      $clr-orange-400      $clr-orange-600,\n  \"deep-orange\": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,\n  \"brown\":       $clr-brown-500       $clr-brown-400       $clr-brown-700,\n  \"grey\":        $clr-grey-600        $clr-grey-500        $clr-grey-700,\n  \"blue-grey\":   $clr-blue-grey-600   $clr-blue-grey-500   $clr-blue-grey-700\n) {\n\n  // Color palette\n  [data-md-color-primary=\"#{$name}\"] {\n    --md-primary-fg-color:             hsla(#{hex2hsl(nth($colors, 1))}, 1);\n    --md-primary-fg-color--light:      hsla(#{hex2hsl(nth($colors, 2))}, 1);\n    --md-primary-fg-color--dark:       hsla(#{hex2hsl(nth($colors, 3))}, 1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-primary-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-primary-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-primary-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-primary-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: white\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"white\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light:        hsla(0, 0%, 0%, 0.54);\n\n  // Typeset color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // [tablet portrait +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 0%, 0.07);\n\n      // Search icon color\n      + .md-search__icon {\n        color: hsla(0, 0%, 0%, 0.87);\n      }\n\n      // Placeholder color\n      &::placeholder {\n        color: hsla(0, 0%, 0%, 0.54);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 0%, 0.32);\n      }\n    }\n  }\n\n  // [screen +]: Add bottom border for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: black\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"black\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Text color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // Header\n  .md-header {\n    background-color: hsla(0, 0%, 0%, 1);\n  }\n\n  // [tablet portrait -]: Layered navigation\n  @include break-to-device(tablet portrait) {\n\n    // Repository information container\n    .md-nav__source {\n      background-color: hsla(0, 0%, 0%, 0.87);\n    }\n  }\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 100%, 0.12);\n\n      // Search form on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.3);\n      }\n    }\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Site title in main navigation\n    html & .md-nav--primary .md-nav__title[for=\"__drawer\"] {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n\n  // [screen +]: Set background color for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Only use dark mode on screens\n@media screen {\n\n  // Slate theme, i.e. dark mode\n  [data-md-color-scheme=\"slate\"] {\n\n    // Slate's hue in the range [0,360] - change this variable to alter the tone\n    // of the theme, e.g. to make it more redish or greenish. This is a slate-\n    // specific variable, but the same approach may be adapted to custom themes.\n    --md-hue: 232;\n\n    // Default color shades\n    --md-default-fg-color:             hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light:      hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter:    hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest:   hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color:             hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light:      hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter:    hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest:   hsla(var(--md-hue), 15%, 21%, 0.07);\n\n    // Code color shades\n    --md-code-fg-color:                hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color:                hsla(var(--md-hue), 15%, 15%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-blue-a200)}, 0.15);\n    --md-code-hl-number-color:         hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color:       hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color:       hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color:         hsla(150, 58%, 44%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-blue-a200)}, 0.3);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color:     hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color:     hsla(var(--md-hue), 15%, 14%, 1);\n\n    // Admonition color shades\n    --md-admonition-bg-color:          hsla(var(--md-hue), 0%, 100%, 0.025);\n\n    // Footer color shades\n    --md-footer-bg-color:              hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark:        hsla(var(--md-hue), 15%, 10%, 1);\n\n    // Black and white primary colors\n    &[data-md-color-primary=\"black\"],\n    &[data-md-color-primary=\"white\"] {\n\n      // Typeset color shades\n      --md-typeset-a-color:            hsla(#{hex2hsl($clr-indigo-400)}, 1);\n    }\n  }\n}\n"]}
\ No newline at end of file
index 3e6a449253f1ae728480e5788f835577f060bcd5..14ce9737ffaa9073273a26595c0ca88db538fa31 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href=".." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href=".." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href=".." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href=".." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2235,6 +2231,7 @@ page would render. The included templates <code>header</code> and <code>footer</
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2285,10 +2282,10 @@ page would render. The included templates <code>header</code> and <code>footer</
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e4bf2048ac62092a7e0b8177630c06b021703b38..12d6459f27ee162fc50920b2b3f1a5128b69f5fe 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL(".",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": ".", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": ".", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3ff72a68add6d6fb4b9bb11d68f9d0c9b897d281..faffb3b5ccc8f1e9ec570574759c9fff2ecc6ec1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1890,6 +1886,7 @@ class <code>jsImageViewer</code> that points to the full version.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1954,10 +1951,10 @@ class <code>jsImageViewer</code> that points to the full version.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b583f95a6de686b0fce7269e65d26328591ddacf..3a3c35caa3eff2e0bd440c78ec1b48a162cc0b6f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2040,6 +2036,7 @@ entirely if you do not provide a tiny build for your file.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2104,10 +2101,10 @@ entirely if you do not provide a tiny build for your file.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dacfea2a041538ba1ac90c9d0083a10b9eaf8d82..12a01d297f5cad5aaf46d112e4af3f9f2b872941 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2491,6 +2487,7 @@ to learn more about this function.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2555,10 +2552,10 @@ to learn more about this function.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index d81da40efa02e1e4ea466bc277e9a63c3c40bc96..fa63650f0edc5c00c8d7e37d0768b218a433509d 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2201,6 +2197,7 @@ construct in ES5, they are represented by mere objects that act as an instance.<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2265,10 +2262,10 @@ construct in ES5, they are represented by mere objects that act as an instance.<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 768e45a85f6cd89eea91847e89eb88d355fa9d8a..0d7f2b401730feb8f0c50a566c6216862771d4a7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2334,6 +2330,7 @@ modules from altering the behavior. It is strongly  recommended to always use
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2398,10 +2395,10 @@ modules from altering the behavior. It is strongly  recommended to always use
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 2d37c3a9cf7fede2bba2477f156c854156958796..155c4ba61a004ba4e585197336882fb7f8bd5397 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2129,6 +2125,7 @@ recommended to use feature detection instead.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2193,10 +2190,10 @@ recommended to use feature detection instead.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 80878354f15fa18d3e6bd7c348df163da8b12ff9..3d79ac05a65c424ab55414550b25d19bca6aee0c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2285,6 +2281,7 @@ function of <code>escapeHTML()</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2349,10 +2346,10 @@ function of <code>escapeHTML()</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6a0f83191035d56cd4a77cdf968ccbd59919f726..8661b9d92bd3444288afd341e221b10af9ef1e2f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2247,6 +2243,7 @@ the key is not part of the collection.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2311,10 +2308,10 @@ the key is not part of the collection.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index af0ae728ff4bf5db6ce7da197e64a2a9e4c63726..aa62b65eda8bd3d40ca4e8df7999be9c97119ab0 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2282,6 +2278,7 @@ key is <code>.content</code> which holds a reference to the dialog's inner conte
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2346,10 +2343,10 @@ key is <code>.content</code> which holds a reference to the dialog's inner conte
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 79dbd6540bd63cf6e973a78818b08540b0d024d8..d3a0f634e90d645d6119ea274d515cf9cb797ba7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2091,6 +2087,7 @@ listener that does not rely on a <code>MutationObserver</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2155,10 +2152,10 @@ listener that does not rely on a <code>MutationObserver</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0134047553d613cd0c4e4b53c34b9a1068ae80ca..011173ca5d6ed4395f3476627e5dfa36d6a0894a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2170,6 +2166,7 @@ of <code>suffix</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2234,10 +2231,10 @@ of <code>suffix</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e047315ac1867270f249cc06be79e81596a0f658..37bae8ee50ae6a071a3b095dd3fc78a3b0758cb4 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2374,6 +2370,7 @@ message for Ajax based actions.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2438,10 +2435,10 @@ message for Ajax based actions.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 30c21b043fc6d6f8aa766913aefe80b51df23b0e..d4418ead56bceead8958658e9ef5f5af92eb8935 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2151,6 +2147,7 @@ it is strongly recommended to use the aliases for consistency.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2215,10 +2212,10 @@ it is strongly recommended to use the aliases for consistency.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 717320bd62bc6c3195763fb31897c7aa6befe877..2e8b67a1b09eeb7ad0e452feea0f1f605f8daa68 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6db733ae6afc84d5a65bf6a5d4e6033b0a02637d..9cfbd9407b7ca5c9ac1c30a4842fdff83cdead3d 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 729c86391a721cb9660b9d1dc91a451c357fca99..32ee1a2f47601a58a8fe03300ac5acb17bbc65d8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a742dae1ba577ca041602ef23f18e9378903cbcb..ace919df2a9c5014040a6a2f6630df209309ea38 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f19295c3b6e833cd8d9da691a196a6371ebdf9e1..061d40b7b59c3d795756984657546ddcc3216cfa 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 590528ba1dbc8cefd9e8ae866326c14b9cbe6019..153a137d32d17aa211167fcf6931cb08d2f817c9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index aab83f796654fc2f5909e1675c55509fb177d02d..7ed9e7178d067d1dd828a4c214eaa78d04d8e9b4 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dbd68da13d3b371d8ffc234c0001a09cd735a45f..0cc62f95d4c865acc55d201dbfc870d1839a6eb6 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b03f6f8aa30e27a16caae13914036c31749af723..9f83dd5bedcacc90641dec5402b86d210df12917 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index eef944a143b2b39c951297d3075907d9fb2c516d..c63e042e9bafeaa455b0b0efc6c22ed22f0d789c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2242,6 +2238,7 @@ All changes are due to extending <code>AbstractFormBuilderForm</code>:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2273,10 +2270,10 @@ All changes are due to extending <code>AbstractFormBuilderForm</code>:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6144c1edc477e94075fabfbe85a3abb6fbe7ddc9..09de994d55ccee5fbd57f195842e11c58e06f15b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 35cee598fb119a12248f3499078d0a7af827a471..273c7e2b75b8b491d6c56b1fc7146584c0a8a14c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2065,6 +2061,7 @@ You can find examples of how to migrate existing forms to form builder <a href="
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2129,10 +2126,10 @@ You can find examples of how to migrate existing forms to form builder <a href="
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 58edfe19c4e94d17b44638b9ef461c022069562e..dd3f1816459800e19c46f770d1c1b315355f13a1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1909,6 +1905,7 @@ Have a <a href="https://github.com/WoltLab/WCF/blob/ce163806c468763f6e3b04e4bf73
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1973,10 +1970,10 @@ Have a <a href="https://github.com/WoltLab/WCF/blob/ce163806c468763f6e3b04e4bf73
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9f51941455ad07f2f967fb21ac083c294a91f03b..b0af336b6c2016474053d71c0d675d44695b6267 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2041,6 +2037,7 @@ The usual pattern of creating a thumbnail for an existing image would then look
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2105,10 +2102,10 @@ The usual pattern of creating a thumbnail for an existing image would then look
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 2e9a84a29f5358f7a2063bfbdd068864881c53c7..612d08db803e78c9e17c8cd29bde0e501182b891 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2014,6 +2010,7 @@ We unified the approach for such links:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2078,10 +2075,10 @@ We unified the approach for such links:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index cde8f400cc6abe3dfa15dce57c85edaf9dadd558..52a194094cfe6f3ec3e4328980c852dfdf7f17c2 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1909,27 +1905,27 @@ Additionally, we have added a new <code>{objectAction}</code> template plugin, w
    These elements can be generated with the <code>{objectAction}</code> template plugin for the <code>delete</code> and <code>toggle</code> action.</li>
 </ol>
 <p>Example:</p>
-<div class="highlight"><pre><span></span><code>&lt;table class=&quot;table jsObjectActionContainer&quot; {*
-    *}data-object-action-class-name=&quot;wcf\data\foo\FooAction&quot;&gt;
-    &lt;thead&gt;
-        &lt;tr&gt;
-            {* … *}
-        &lt;/tr&gt;
-    &lt;/thead&gt;
-
-    &lt;tbody&gt;
-        {foreach from=$objects item=foo}
-            &lt;tr class=&quot;jsObjectActionObject&quot; data-object-id=&quot;{@$foo-&gt;getObjectID()}&quot;&gt;
-                &lt;td class=&quot;columnIcon&quot;&gt;
-                    {objectAction action=&quot;toggle&quot; isDisabled=$foo-&gt;isDisabled}
-                    {objectAction action=&quot;delete&quot; objectTitle=$foo-&gt;getTitle()}
-                    {* … *}
-                &lt;/td&gt;
-                {* … *}
-            &lt;/tr&gt;
-        {/foreach}
-    &lt;/tbody&gt;
-&lt;/table&gt;
+<div class="highlight"><pre><span></span><code><span class="x">&lt;table class=&quot;table jsObjectActionContainer&quot; </span><span class="cp">{</span><span class="c">*</span>
+<span class="c">    *</span><span class="cp">}</span><span class="x">data-object-action-class-name=&quot;wcf\data\foo\FooAction&quot;&gt;</span>
+<span class="x">    &lt;thead&gt;</span>
+<span class="x">        &lt;tr&gt;</span>
+<span class="x">            </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">        &lt;/tr&gt;</span>
+<span class="x">    &lt;/thead&gt;</span>
+
+<span class="x">    &lt;tbody&gt;</span>
+<span class="x">        </span><span class="cp">{</span><span class="nf">foreach</span> <span class="na">from</span><span class="o">=</span><span class="nv">$objects</span> <span class="na">item</span><span class="o">=</span><span class="na">foo</span><span class="cp">}</span><span class="x"></span>
+<span class="x">            &lt;tr class=&quot;jsObjectActionObject&quot; data-object-id=&quot;</span><span class="cp">{</span><span class="o">@</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">getObjectID</span><span class="o">()</span><span class="cp">}</span><span class="x">&quot;&gt;</span>
+<span class="x">                &lt;td class=&quot;columnIcon&quot;&gt;</span>
+<span class="x">                    </span><span class="cp">{</span><span class="nf">objectAction</span> <span class="na">action</span><span class="o">=</span><span class="s2">&quot;toggle&quot;</span> <span class="na">isDisabled</span><span class="o">=</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">isDisabled</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                    </span><span class="cp">{</span><span class="nf">objectAction</span> <span class="na">action</span><span class="o">=</span><span class="s2">&quot;delete&quot;</span> <span class="na">objectTitle</span><span class="o">=</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">getTitle</span><span class="o">()</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                    </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                &lt;/td&gt;</span>
+<span class="x">                </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">            &lt;/tr&gt;</span>
+<span class="x">        </span><span class="cp">{</span><span class="nf">/foreach</span><span class="cp">}</span><span class="x"></span>
+<span class="x">    &lt;/tbody&gt;</span>
+<span class="x">&lt;/table&gt;</span>
 </code></pre></div>
 <p>Please refer to the documentation in <a href="https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/template/plugin/ObjectActionFunctionTemplatePlugin.class.php"><code>ObjectActionFunctionTemplatePlugin</code></a> for details and examples on how to use this template plugin.</p>
 <p>The relevant TypeScript module registering the event listeners on the object action buttons is <a href="https://github.com/WoltLab/WCF/blob/master/ts/WoltLabSuite/Core/Ui/Object/Action.ts"><code>Ui/Object/Action</code></a>.
@@ -1983,7 +1979,7 @@ To also cover scenarios in which there are fixed child elements that should not
 <div class="md-source-date">
   <small>
     
-      Last update: 2021-03-24
+      Last update: 2021-04-06
     
   </small>
 </div>
@@ -1998,6 +1994,7 @@ To also cover scenarios in which there are fixed child elements that should not
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2062,10 +2059,10 @@ To also cover scenarios in which there are fixed child elements that should not
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5bbf07aacfd9fe20772c0cc67415b793ec845da5..6cdd0a387b1af2539ef3727dd816f2cb3bf7cdc3 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1962,6 +1958,7 @@ Refer to <a href="https://paragonie.com/blog/2016/06/constant-time-encoding-bori
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2026,10 +2023,10 @@ Refer to <a href="https://paragonie.com/blog/2016/06/constant-time-encoding-bori
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a3d03fdf06a11f7b04afaccda1c76dd9e6ca07e8..ca13e189cdc3f7c74ad8e52c0895b3d75ddd77a8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2080,6 +2076,7 @@ If your custom <code>IUserAvatar</code> implementation supports image types with
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2144,10 +2141,10 @@ If your custom <code>IUserAvatar</code> implementation supports image types with
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 012595fcf1a16aaf08492a9d17a1becf61b86ef2..e601bc0022f175d11d360d2f68c2715d7cde27ef 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2294,6 +2290,7 @@ The <code>{csrfToken}</code> tag is a drop-in replacement and was backported to
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2358,10 +2355,10 @@ The <code>{csrfToken}</code> tag is a drop-in replacement and was backported to
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c0de283c4aa5823bba2c8fb97bf78c8e810ab766..45dcbfeaaf15be68a9673f870ac42e649f37b18b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1895,6 +1891,7 @@ See <a href="https://github.com/WoltLab/WCF/pull/3612">WoltLab/WCF#3612</a> for
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1959,10 +1956,10 @@ See <a href="https://github.com/WoltLab/WCF/pull/3612">WoltLab/WCF#3612</a> for
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a28b11b36828257a83efc0ff286bc2836d4677c4..7636df7ab30ad179290853d0b0c910336df3176b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2022,6 +2018,7 @@ If it is not present, <code>DatabaseTable::indices()</code> will automatically s
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2086,10 +2083,10 @@ If it is not present, <code>DatabaseTable::indices()</code> will automatically s
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5ef3a4d9662399de23d4b18d2bce16a8427dacda..8e672484e2df988c7d785faa8431d81bc994c2de 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2482,6 +2478,7 @@ Instead of using a dummy <code>&lt;instruction&gt;</code> that idempotently upda
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2546,10 +2543,10 @@ Instead of using a dummy <code>&lt;instruction&gt;</code> that idempotently upda
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a33366223b670a0ad9ea2a63b1f856ffc41c0d42..4dfebdbd158547619be40e11099d9c1ad88620ba 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a17cd1c75cd8fae585ea27ab195b97f5c379ff25..7f5b993223600ed174ad91027718963deb86701f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2055,6 +2051,7 @@ the full external link otherwise.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2119,10 +2116,10 @@ the full external link otherwise.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e13589d25d23d511e3c45972a73a8b1b2db80e3c..af19b8581b3d341876507abaabe1b3c669d12401 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1955,6 +1951,7 @@ the class has to implement the <code>wcf\system\search\acp\IACPSearchResultProvi
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2019,10 +2016,10 @@ the class has to implement the <code>wcf\system\search\acp\IACPSearchResultProvi
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5df9b81f2224498d60af19762bc45ee16289b882..a1ed9fe23246795923037bd81f57cf8047ee87ec 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1962,6 +1958,7 @@ If no <code>application</code> attribute is given, the following rules are appli
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2026,10 +2023,10 @@ If no <code>application</code> attribute is given, the following rules are appli
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c39dfa66a0826a676ea32dbc1c88b527430d6c34..8cbaa8eae969bba15229d8f4064c7ca926a0eda5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2169,6 +2165,7 @@ The <code>name</code> attribute is a 0-indexed integer.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2233,10 +2230,10 @@ The <code>name</code> attribute is a 0-indexed integer.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 905d323825c24a987a00fbce17a72c6700201763..11bfe14dea91acf85d436174d99bc4b39a676155 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0e3b5e82595c57b7c6a7133a59bbbd9f80ec6dca..b7b2ab8481c93230a602234d024ee76e1bc74a04 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1988,6 +1984,7 @@ The class has to implement the <code>wcf\system\clipboard\action\IClipboardActio
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2052,10 +2049,10 @@ The class has to implement the <code>wcf\system\clipboard\action\IClipboardActio
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6858653525159f8261aba65cd7c38fe66d90af1b..c293a9a5f6665c5140d342d7c090e880fea2c194 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index d02f42b925af6b8ffd7a0c49285e079979b266c8..147115e1e9ee05bdb5523221119d4b8825716679 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2033,6 +2029,7 @@ They correspond to the fields in <code>crontab(5)</code> of a cron daemon and ac
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2097,10 +2094,10 @@ They correspond to the fields in <code>crontab(5)</code> of a cron daemon and ac
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 680e9d23f74665073b15ae7e470cba35015269c9..88cabc337f7916345eda7beb3c24deb226affe81 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1907,6 +1903,7 @@ If you're updating from <code>1.0.0</code> to <code>1.0.1</code>, <code>&lt;targ
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1938,10 +1935,10 @@ If you're updating from <code>1.0.0</code> to <code>1.0.1</code>, <code>&lt;targ
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f332a7541c952844736fd470c30c743098ba250c..6679fca28679ba40af9380f870da3610f87e8178 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2078,6 +2074,7 @@ If the nice value of two event listeners is equal, they are sorted by the listen
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2142,10 +2139,10 @@ If the nice value of two event listeners is equal, they are sorted by the listen
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1d569718df5cac525ab358917c15c1f6a196d415..80aec3b2f96b0bc0bcd0aeebf4629ae06c950e21 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1915,6 +1911,7 @@ The file path given in the <code>instruction</code> element as its value must be
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1979,10 +1976,10 @@ The file path given in the <code>instruction</code> element as its value must be
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 8a11eaeea0f4925585347bcf16c1dc5f4efcfd9e..cbe0af02dd5dd137558e54ad4366629c0b4c3284 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3ad76f7cc88036cdffe0f33b9ebce7542b5472e5..ed31142a3d769ef27b70a5681f913e7e306bad68 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6fb9b3ba56b13caa1ed45d06d891071d30a3c60c..0ad3fac4c240f2b545b006348ca638ff98df513b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 875cc76bdbd3b02f91e4887b32ec571e07b0f809..2653064648d4c8655178b802ba8a0b8e09bc3c1c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index af3c82859d41fedd021e7fc61fa06f01e1f46974..08e9696f83cb7c6a099053dbec43d80c0f5e4bf1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 596a9f3b4f4e166e0dc02b79aace0f56da8acd75..8a2f9472238d4f8bf5cc88d425d8f886be4cc72c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1945,6 +1941,7 @@ Posts are then registered as an object type, implementing the “taggable conten
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2009,10 +2006,10 @@ Posts are then registered as an object type, implementing the “taggable conten
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c8d6c40e729b424b1559520ef562820f072ad5df..4b5f9338e5bfa1ec10bf1d4a19b27fdc7caeece7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1975,6 +1971,7 @@ Refer to the documentation of these for further explanation.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2039,10 +2036,10 @@ Refer to the documentation of these for further explanation.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1945e20e47ba437979cfe653632bc28c510e95a4..7b476b573541c1115a0757108eb70298acd10b80 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2314,6 +2310,7 @@ DESC:wcf.global.sortOrder.descending<span class="nt">&lt;/selectoptions&gt;</spa
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2378,10 +2375,10 @@ DESC:wcf.global.sortOrder.descending<span class="nt">&lt;/selectoptions&gt;</spa
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 116dc2c1792c61e101e6ed51ca74630aa3fdb2aa..721e94828a4db40e9729ad37c0deb64bab01e9eb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2198,6 +2194,7 @@ page would be presented with a permission denied message.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2262,10 +2259,10 @@ page would be presented with a permission denied message.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 4d5ecc1b9cb1ea4842ea48a48d0191bbeed5227d..424c6ad17d301ec94efb9e4ec0a2fd0d68e2d6cd 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dc21b07afbdd61ef4d07721b69303961ccd7274e..2308d7ccb0bdd8111d980c9859c992b5e2173ceb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1987,6 +1983,7 @@ This grants you access to the class members, including <code>$this-&gt;installat
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2051,10 +2048,10 @@ This grants you access to the class members, including <code>$this-&gt;installat
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b4f921f0066af0549c98239393fa06e138653eec..d44aa12b9d15749ddebb5b15a2661ee73c159f4c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1995,6 +1991,7 @@ Aliases must be separated by a line feed character (<code>\n</code>, U+000A).</p
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2059,10 +2056,10 @@ Aliases must be separated by a line feed character (<code>\n</code>, U+000A).</p
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 78537cafb49848733ba633fe69d4492803bc2764..216925e2d47e3dfe0de7a7990947e868114fa7b8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2063,6 +2059,7 @@ If you really need triggers, you should consider adding them by custom SQL queri
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2127,10 +2124,10 @@ If you really need triggers, you should consider adding them by custom SQL queri
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b22f8a30ef88e6220eb18bf866f7312cf4d83e9a..9a4676fa6c94ba806bd2295937641ac675a6b589 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1887,6 +1883,7 @@ Please use the ACP's export mechanism to export styles.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1951,10 +1948,10 @@ Please use the ACP's export mechanism to export styles.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b42529d5d16afdc938abfb0a2c50c217c728ab43..32cd5bbc0e6276222fd9ccd8cda7d60fd1a67c7f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2058,6 +2054,7 @@ If the nice value of two template listeners is equal, the order is undefined.</p
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2122,10 +2119,10 @@ If the nice value of two template listeners is equal, the order is undefined.</p
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0a1dad3223b5ac5203b2b01e3e1f5da7bde28b7a..258f540566b6516f1b1332b64d9ee9e9f910151b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 36c4d02dfc255164700e31c1df6fd6deba9cadae..3529a648ca9d640ea28f3930d889ee2c1feb4c3b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2031,6 +2027,7 @@ If you want to provide an optional description of the option, you have to provid
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2095,10 +2092,10 @@ If you want to provide an optional description of the option, you have to provid
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6bfa175e84c49e8440e1a7b4cb2b17f19a8ae535..c5ca6ed9c46cf9a3a6e535940264ddfaf1954a66 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2072,6 +2068,7 @@ the class has to implement the <code>wcf\system\menu\user\IUserMenuItemProvider<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2136,10 +2133,10 @@ the class has to implement the <code>wcf\system\menu\user\IUserMenuItemProvider<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9c62148d00bb08ae9c25bf28818bfe4fceed5793..e48dc131b11a4f39b17f385d84af043240724123 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2030,6 +2026,7 @@ Defines whether this type of email notifications is enabled by default.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2094,10 +2091,10 @@ Defines whether this type of email notifications is enabled by default.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dcab598a0e2506bc96659ea57d4e4d978cbb8af3..328bcac12b9c06fd71bf18806588a0c0c143dd8a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2122,6 +2118,7 @@ If you want to provide an optional description of the option, you have to provid
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2186,10 +2183,10 @@ If you want to provide an optional description of the option, you have to provid
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9307b3baedd679ab4d19fff0d735775698f05aa2..402d90d93fb9beed57f4c460be5419600a8d7e4a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1990,6 +1986,7 @@ the class has to implement the <code>wcf\system\menu\user\profile\content\IUserP
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2054,10 +2051,10 @@ the class has to implement the <code>wcf\system\menu\user\profile\content\IUserP
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 34e984a7e59f26c1cc74502fbef8597b4f8ffb1b..dafda9ecce4fab7bd06df500b14299ada2a06bfc 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1990,6 +1986,7 @@ at least how it was implemented - was really necessary.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2054,10 +2051,10 @@ at least how it was implemented - was really necessary.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index bb4971566ffecb448cb7cfe14b0c93366afa172f..7d8ad4354cc2b8f09d65e4d6bd76a06aa309b691 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1940,6 +1936,7 @@ rebuild for whatever reason.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1971,10 +1968,10 @@ rebuild for whatever reason.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 544c37304849a86b38de00d7df12894ac2f2f636..eacd337d51f695edcb66fd16bbee1163ef8961be 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1918,6 +1914,7 @@ In most instances, you only need to set the <code>AbstractRuntimeCache::$listCla
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1949,10 +1946,10 @@ In most instances, you only need to set the <code>AbstractRuntimeCache::$listCla
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 60f8d8bb54b8828f7a07fa9a7f91f645b4761f23..a2922e36f4e9390939cd6b178bc5025a8224b46a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a643076e4b58d58926c0064838d0e1643af3f89c..99011acb1c9c3d0c106f9ab449dd850b27fef896 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1957,6 +1953,7 @@ EOT
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2021,10 +2018,10 @@ EOT
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f6fae1dcd05a29cf96fff32f1224597e5ee3aa9f..8a7fa1390e9f28c1066a8236008eb348f7c51d29 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 67a1ea6fa31f6633e330e3ca8abfc47f446d519d..04be273b6dced4fdd5bcab8569fa739f62371622 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2182,6 +2178,7 @@ Furthermore, the type-hinting of the parameter illustrates in which contexts the
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2246,10 +2243,10 @@ Furthermore, the type-hinting of the parameter illustrates in which contexts the
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5a939d23d27ca704b4fbc111b0cd5dc7503c74ec..1adb0479ad7a32379dbd074de1c9eea2a08c6849 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1999,6 +1995,7 @@ Every form container has to create a matching form container dependency object f
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2063,10 +2060,10 @@ Every form container has to create a matching form container dependency object f
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 40fe6da9c8c9802730addb10cb296fd3570642c9..fc936439e228fc291b625741e62905dbfa8f151a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -3032,6 +3028,7 @@ This trait provides <code>getWysiwygId()</code> and <code>wysiwygId($wysiwygId)<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -3096,10 +3093,10 @@ This trait provides <code>getWysiwygId()</code> and <code>wysiwygId($wysiwygId)<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 8991e1ef7dab50bdd992eafdfcf46643dbf7e131..c02853060f03cab424b676b6b20cd59e1a4c7645 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2027,6 +2023,7 @@ The constructor of <code>FormBuilderDialog</code> expects the following paramete
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2091,10 +2088,10 @@ The constructor of <code>FormBuilderDialog</code> expects the following paramete
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0be00f7c29487f3d48ca2122f7dac0407d97747c..ee96e02aa63e75e47ce17891665f7336d81aa0c5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2753,6 +2749,7 @@ The implementing class has to implement the methods <code>suffix($languageItem =
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2817,10 +2814,10 @@ The implementing class has to implement the methods <code>suffix($languageItem =
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0803f44e146dc85ddccd7069be46a2ade6b9c510..d0643db16b8da56f36befa39a72dfe5ffffd7ecb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2099,6 +2095,7 @@ When the data processor is invoked, it checks whether the relevant entry in the
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2163,10 +2160,10 @@ When the data processor is invoked, it checks whether the relevant entry in the
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3e24f852d2c7f038440ac51f31a5824f94feb862..e47f465a7974923d91a8218ea859079f8a0c2c58 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2281,6 +2277,7 @@ This allows options to store arbitrary data which can be accessed but were not i
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2345,10 +2342,10 @@ This allows options to store arbitrary data which can be accessed but were not i
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5659e77644493978fe426e9957e991ef8d80bc3f..b8584cc4f400fc6226f6f4c12cf64cc9e63eeeb7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1914,6 +1910,7 @@ The language variable follows the pattern <code>wcf.acp.sitemap.objectType.{obje
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1978,10 +1975,10 @@ The language variable follows the pattern <code>wcf.acp.sitemap.objectType.{obje
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 4712a642f506472ee96e73c18b347b6d7f41ab00..f0e74309bfb7706210387cf45e65cbddcbcfae0a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1867,6 +1863,7 @@ For this form and the user activity point list shown in the frontend, you have t
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1931,10 +1928,10 @@ For this form and the user activity point list shown in the frontend, you have t
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0ea98f61962bf6d9cdc59281e7ec3bd43b607cc3..f7669139ac311d574ffd32d7ac318cd15afa0254 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2240,6 +2236,7 @@ In this case, you can use <code>UserNotificationHandler::markAsConfirmed()</code
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2304,10 +2301,10 @@ In this case, you can use <code>UserNotificationHandler::markAsConfirmed()</code
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9fc9166667ff11940b35f0eedb0c731f7826214f..9465a55421960c5e64915d15ff087a17297e446b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2101,6 +2097,7 @@ it includes everything that is required for a basic app.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2165,10 +2162,10 @@ it includes everything that is required for a basic app.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9c80e6f90f7f53d303e3bf55a51d0e7057304027..2e52ec4e0d4c8448afb5d157d569d0f62ebf3923 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2001,6 +1997,7 @@ The third line tells the IDE (if <code>@mixin</code> is supported) that the data
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2032,10 +2029,10 @@ The third line tells the IDE (if <code>@mixin</code> is supported) that the data
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f30fa53513b11d51dc0de6474f4b82d70e22e6b8..41c11e7e139258ca79f83074cf70ac5731c67dfb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2207,6 +2203,7 @@ Therefore, the line must be split into multiple lines with each argument in a se
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2271,10 +2268,10 @@ Therefore, the line must be split into multiple lines with each argument in a se
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6fa1352c9316917a9317f74f1c2bc68ec66fad84..3141d8545181d9209aa551201038e84adf4c8c59 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2134,6 +2130,7 @@ This case is covered by <code>PreparedStatement::fetchMap()</code>:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2198,10 +2195,10 @@ This case is covered by <code>PreparedStatement::fetchMap()</code>:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 85cee9c3ca77ea2b7596de438f9bc96f706f1637..d3236f67ef6545195fbe8a751034a2007f8afdaf 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2314,6 +2310,7 @@ This can be achieved by setting the <code>$objectClassName</code> property to th
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2378,10 +2375,10 @@ This can be achieved by setting the <code>$objectClassName</code> property to th
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 91422f345a5c541103ee8be4874ff998929d1a04..2910079322ed922394788af1cf0c59644ad02a6b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1943,6 +1939,7 @@ All of the exceptions are found in the <code>wcf\system\exception</code> namespa
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2007,10 +2004,10 @@ All of the exceptions are found in the <code>wcf\system\exception</code> namespa
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index bf881986203cb2f902ba32efd1a85f813181bc23..cd8e6710b271b64ca05f2c2704676f647c5e97e5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2094,6 +2090,7 @@ because it does not contain personal data, such as internal data.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2158,10 +2155,10 @@ because it does not contain personal data, such as internal data.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5fc2cb0cdc03645df01a070e7c9e4c40767930b0..9a60ab0787af74cb9e30e9014901f7cb7242e717 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 32cf8ab868d108f0ef4414a3a05e81f002314549..41151547fb17df4ef54701d36eed57745dbe4d27 100644 (file)
@@ -1 +1 @@
-{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"WoltLab Suite 5.4 Documentation # Introduction # This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more. About WoltLab Suite # WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"WoltLab Suite 5.4 Documentation"},{"location":"#woltlab-suite-54-documentation","text":"","title":"WoltLab Suite 5.4 Documentation"},{"location":"#introduction","text":"This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more.","title":"Introduction"},{"location":"#about-woltlab-suite","text":"WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"About WoltLab Suite"},{"location":"getting-started/","text":"Creating a simple package # Setup and Requirements # This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows The package.xml File # We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is. The PHP Class # The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all. The Template # Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance. The Page Definition # The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus. Building the Package # If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition. Installation # Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package! Developer Tools # This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed. Registering a Project # Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml . Synchronizing # The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed. Appendix # Template Guessing # The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Getting Started"},{"location":"getting-started/#creating-a-simple-package","text":"","title":"Creating a simple package"},{"location":"getting-started/#setup-and-requirements","text":"This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows","title":"Setup and Requirements"},{"location":"getting-started/#the-packagexml-file","text":"We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is.","title":"The package.xml File"},{"location":"getting-started/#the-php-class","text":"The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all.","title":"The PHP Class"},{"location":"getting-started/#the-template","text":"Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance.","title":"The Template"},{"location":"getting-started/#the-page-definition","text":"The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus.","title":"The Page Definition"},{"location":"getting-started/#building-the-package","text":"If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition.","title":"Building the Package"},{"location":"getting-started/#installation","text":"Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package!","title":"Installation"},{"location":"getting-started/#developer-tools","text":"This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed.","title":"Developer Tools"},{"location":"getting-started/#registering-a-project","text":"Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml .","title":"Registering a Project"},{"location":"getting-started/#synchronizing","text":"The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed.","title":"Synchronizing"},{"location":"getting-started/#appendix","text":"","title":"Appendix"},{"location":"getting-started/#template-guessing","text":"The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Template Guessing"},{"location":"javascript/code-snippets/","text":"Code Snippets - JavaScript API # This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function. ImageViewer # The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"Code Snippets"},{"location":"javascript/code-snippets/#code-snippets-javascript-api","text":"This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function.","title":"Code Snippets - JavaScript API"},{"location":"javascript/code-snippets/#imageviewer","text":"The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"ImageViewer"},{"location":"javascript/general-usage/","text":"General JavaScript Usage # The History of the Legacy API # The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base. Embedding JavaScript inside Templates # The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script > Including External JavaScript Files # The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about. Debug-Variants and Cache-Buster # Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The Accelerated Guest View (\"Tiny Builds\") # You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The {js} Template Plugin # The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"General Usage"},{"location":"javascript/general-usage/#general-javascript-usage","text":"","title":"General JavaScript Usage"},{"location":"javascript/general-usage/#the-history-of-the-legacy-api","text":"The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base.","title":"The History of the Legacy API"},{"location":"javascript/general-usage/#embedding-javascript-inside-templates","text":"The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script >","title":"Embedding JavaScript inside Templates"},{"location":"javascript/general-usage/#including-external-javascript-files","text":"The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about.","title":"Including External JavaScript Files"},{"location":"javascript/general-usage/#debug-variants-and-cache-buster","text":"Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"Debug-Variants and Cache-Buster"},{"location":"javascript/general-usage/#the-accelerated-guest-view-tiny-builds","text":"You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"The Accelerated Guest View (\"Tiny Builds\")"},{"location":"javascript/general-usage/#the-js-template-plugin","text":"The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"The {js} Template Plugin"},{"location":"javascript/helper-functions/","text":"JavaScript Helper Functions # Introduction # Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction. Elements # elCreate(tagName: string): Element # Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" ); elRemove(element: Element) # Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element ); elShow(element: Element) # Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" ); elHide(element: Element) # Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" ); elToggle(element: Element) # Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() . Attributes # elAttr(element: Element, attribute: string, value?: string): string # Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" ); elAttrBool(element: Element, attribute: string): boolean # Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish } elData(element: Element, attribute: string, value?: string): string # Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" ); elDataBool(element: Element, attribute: string): boolean # Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish } Selecting Elements # Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error. elById(id: string): Element | null # Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" ); elBySel(selector: string, context?: Element): Element | null # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" ); elBySelAll(selector: string, context?: Element, callback: (element: Element) => void): NodeList # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" ); Callback to Iterate Over Elements # elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element }); elClosest(element: Element, selector: string): Element | null # Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" ); Text Nodes # If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector. elByClass(className: string, context?: Element): NodeList # Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" ); elByTag(tagName: string, context?: Element): NodeList # Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" ); Utility Functions # `elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null`` # Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function. String Extensions # hashCode(): string # Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"Helper Functions"},{"location":"javascript/helper-functions/#javascript-helper-functions","text":"","title":"JavaScript Helper Functions"},{"location":"javascript/helper-functions/#introduction","text":"Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction.","title":"Introduction"},{"location":"javascript/helper-functions/#elements","text":"","title":"Elements"},{"location":"javascript/helper-functions/#elcreatetagname-string-element","text":"Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" );","title":"elCreate(tagName: string): Element"},{"location":"javascript/helper-functions/#elremoveelement-element","text":"Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element );","title":"elRemove(element: Element)"},{"location":"javascript/helper-functions/#elshowelement-element","text":"Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" );","title":"elShow(element: Element)"},{"location":"javascript/helper-functions/#elhideelement-element","text":"Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" );","title":"elHide(element: Element)"},{"location":"javascript/helper-functions/#eltoggleelement-element","text":"Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() .","title":"elToggle(element: Element)"},{"location":"javascript/helper-functions/#attributes","text":"","title":"Attributes"},{"location":"javascript/helper-functions/#elattrelement-element-attribute-string-value-string-string","text":"Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" );","title":"elAttr(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#elattrboolelement-element-attribute-string-boolean","text":"Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish }","title":"elAttrBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#eldataelement-element-attribute-string-value-string-string","text":"Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" );","title":"elData(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#eldataboolelement-element-attribute-string-boolean","text":"Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish }","title":"elDataBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#selecting-elements","text":"Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error.","title":"Selecting Elements"},{"location":"javascript/helper-functions/#elbyidid-string-element-null","text":"Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" );","title":"elById(id: string): Element | null"},{"location":"javascript/helper-functions/#elbyselselector-string-context-element-element-null","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" );","title":"elBySel(selector: string, context?: Element): Element | null"},{"location":"javascript/helper-functions/#elbyselallselector-string-context-element-callback-element-element-void-nodelist","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" );","title":"elBySelAll(selector: string, context?: Element, callback: (element: Element) =&gt; void): NodeList"},{"location":"javascript/helper-functions/#callback-to-iterate-over-elements","text":"elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element });","title":"Callback to Iterate Over Elements"},{"location":"javascript/helper-functions/#elclosestelement-element-selector-string-element-null","text":"Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" );","title":"elClosest(element: Element, selector: string): Element | null"},{"location":"javascript/helper-functions/#text-nodes","text":"If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector.","title":"Text Nodes"},{"location":"javascript/helper-functions/#elbyclassclassname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" );","title":"elByClass(className: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#elbytagtagname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" );","title":"elByTag(tagName: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#utility-functions","text":"","title":"Utility Functions"},{"location":"javascript/helper-functions/#elinnererrorelement-element-errormessage-string-ishtml-boolean-element-null","text":"Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function.","title":"`elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null``"},{"location":"javascript/helper-functions/#string-extensions","text":"","title":"String Extensions"},{"location":"javascript/helper-functions/#hashcode-string","text":"Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"hashCode(): string"},{"location":"javascript/legacy-api/","text":"Legacy JavaScript API # Introduction # The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API. Classes # Singletons # Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; }); Regular Classes # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); Inheritance # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); Ajax Requests # // App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; }); Phrases # < script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script > Event-Listener # < script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Legacy API"},{"location":"javascript/legacy-api/#legacy-javascript-api","text":"","title":"Legacy JavaScript API"},{"location":"javascript/legacy-api/#introduction","text":"The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API.","title":"Introduction"},{"location":"javascript/legacy-api/#classes","text":"","title":"Classes"},{"location":"javascript/legacy-api/#singletons","text":"Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; });","title":"Singletons"},{"location":"javascript/legacy-api/#regular-classes","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; });","title":"Regular Classes"},{"location":"javascript/legacy-api/#inheritance","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"Inheritance"},{"location":"javascript/legacy-api/#ajax-requests","text":"// App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; });","title":"Ajax Requests"},{"location":"javascript/legacy-api/#phrases","text":"< script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script >","title":"Phrases"},{"location":"javascript/legacy-api/#event-listener","text":"< script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Event-Listener"},{"location":"javascript/new-api_ajax/","text":"Ajax Requests - JavaScript API # Ajax inside Modules # The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed. _ajaxSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; }); Request Settings # The object returned by the aforementioned _ajaxSetup() callback can contain these values: data # Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters . contentType # Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty. responseType # Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess ! type # Defaults to POST . The HTTP Verb used for this request. url # Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true. withCredentials # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint. autoAbort # Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code. ignoreError # Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback. silent # Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator. includeRequestedWith # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint. failure # Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON. finalize # Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest . success # Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued. _ajaxSuccess() # This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests. _ajaxFailure() # Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed. Single Requests Without a Module # The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Ajax"},{"location":"javascript/new-api_ajax/#ajax-requests-javascript-api","text":"","title":"Ajax Requests - JavaScript API"},{"location":"javascript/new-api_ajax/#ajax-inside-modules","text":"The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed.","title":"Ajax inside Modules"},{"location":"javascript/new-api_ajax/#_ajaxsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; });","title":"_ajaxSetup()"},{"location":"javascript/new-api_ajax/#request-settings","text":"The object returned by the aforementioned _ajaxSetup() callback can contain these values:","title":"Request Settings"},{"location":"javascript/new-api_ajax/#data","text":"Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters .","title":"data"},{"location":"javascript/new-api_ajax/#contenttype","text":"Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty.","title":"contentType"},{"location":"javascript/new-api_ajax/#responsetype","text":"Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess !","title":"responseType"},{"location":"javascript/new-api_ajax/#type","text":"Defaults to POST . The HTTP Verb used for this request.","title":"type"},{"location":"javascript/new-api_ajax/#url","text":"Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true.","title":"url"},{"location":"javascript/new-api_ajax/#withcredentials","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint.","title":"withCredentials"},{"location":"javascript/new-api_ajax/#autoabort","text":"Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code.","title":"autoAbort"},{"location":"javascript/new-api_ajax/#ignoreerror","text":"Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback.","title":"ignoreError"},{"location":"javascript/new-api_ajax/#silent","text":"Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator.","title":"silent"},{"location":"javascript/new-api_ajax/#includerequestedwith","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint.","title":"includeRequestedWith"},{"location":"javascript/new-api_ajax/#failure","text":"Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON.","title":"failure"},{"location":"javascript/new-api_ajax/#finalize","text":"Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest .","title":"finalize"},{"location":"javascript/new-api_ajax/#success","text":"Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued.","title":"success"},{"location":"javascript/new-api_ajax/#_ajaxsuccess","text":"This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests.","title":"_ajaxSuccess()"},{"location":"javascript/new-api_ajax/#_ajaxfailure","text":"Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed.","title":"_ajaxFailure()"},{"location":"javascript/new-api_ajax/#single-requests-without-a-module","text":"The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Single Requests Without a Module"},{"location":"javascript/new-api_browser/","text":"Browser and Screen Sizes - JavaScript API # Ui/Screen # CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle. Supported Aliases # You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px) on(query: string, callbacks: Object): string # Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() . remove(query: string, uuid: string) # Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() . is(query: string): boolean # Tests if the provided media query currently matches and returns true on match. scrollDisable() # Temporarily prevents the page from being scrolled, until .scrollEnable() is called. scrollEnable() # Enables page scrolling again, unless another pending action has also prevented the page scrolling. Environment # The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks. browser(): string # Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default) platform(): string # Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"Browser and Screen Sizes"},{"location":"javascript/new-api_browser/#browser-and-screen-sizes-javascript-api","text":"","title":"Browser and Screen Sizes - JavaScript API"},{"location":"javascript/new-api_browser/#uiscreen","text":"CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle.","title":"Ui/Screen"},{"location":"javascript/new-api_browser/#supported-aliases","text":"You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px)","title":"Supported Aliases"},{"location":"javascript/new-api_browser/#onquery-string-callbacks-object-string","text":"Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() .","title":"on(query: string, callbacks: Object): string"},{"location":"javascript/new-api_browser/#removequery-string-uuid-string","text":"Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() .","title":"remove(query: string, uuid: string)"},{"location":"javascript/new-api_browser/#isquery-string-boolean","text":"Tests if the provided media query currently matches and returns true on match.","title":"is(query: string): boolean"},{"location":"javascript/new-api_browser/#scrolldisable","text":"Temporarily prevents the page from being scrolled, until .scrollEnable() is called.","title":"scrollDisable()"},{"location":"javascript/new-api_browser/#scrollenable","text":"Enables page scrolling again, unless another pending action has also prevented the page scrolling.","title":"scrollEnable()"},{"location":"javascript/new-api_browser/#environment","text":"The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks.","title":"Environment"},{"location":"javascript/new-api_browser/#browser-string","text":"Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default)","title":"browser(): string"},{"location":"javascript/new-api_browser/#platform-string","text":"Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"platform(): string"},{"location":"javascript/new-api_core/","text":"Core Modules and Functions - JavaScript API # A brief overview of common methods that may be useful when writing any module. Core # clone(object: Object): Object # Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true }); extend(base: Object, ...merge: Object[]): Object # Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false }); inherit(base: Object, target: Object, merge?: Object) # Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); isPlainObject(object: Object): boolean # Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false }); triggerEvent(element: Element, eventName: string) # Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); }); Language # add(key: string, value: string) # Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script > addObject(object: Object) # Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script > get(key: string, parameters?: Object): string # Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); }); StringUtil # escapeHTML(str: string): string # Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt; escapeRegExp(str: string): string # Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions. lcfirst(str: string): string # Makes a string's first character lowercase. ucfirst(str: string): string # Makes a string's first character uppercase. unescapeHTML(str: string): string # Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"Core Functions"},{"location":"javascript/new-api_core/#core-modules-and-functions-javascript-api","text":"A brief overview of common methods that may be useful when writing any module.","title":"Core Modules and Functions - JavaScript API"},{"location":"javascript/new-api_core/#core","text":"","title":"Core"},{"location":"javascript/new-api_core/#cloneobject-object-object","text":"Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true });","title":"clone(object: Object): Object"},{"location":"javascript/new-api_core/#extendbase-object-merge-object-object","text":"Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false });","title":"extend(base: Object, ...merge: Object[]): Object"},{"location":"javascript/new-api_core/#inheritbase-object-target-object-merge-object","text":"Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"inherit(base: Object, target: Object, merge?: Object)"},{"location":"javascript/new-api_core/#isplainobjectobject-object-boolean","text":"Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false });","title":"isPlainObject(object: Object): boolean"},{"location":"javascript/new-api_core/#triggereventelement-element-eventname-string","text":"Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); });","title":"triggerEvent(element: Element, eventName: string)"},{"location":"javascript/new-api_core/#language","text":"","title":"Language"},{"location":"javascript/new-api_core/#addkey-string-value-string","text":"Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script >","title":"add(key: string, value: string)"},{"location":"javascript/new-api_core/#addobjectobject-object","text":"Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script >","title":"addObject(object: Object)"},{"location":"javascript/new-api_core/#getkey-string-parameters-object-string","text":"Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); });","title":"get(key: string, parameters?: Object): string"},{"location":"javascript/new-api_core/#stringutil","text":"","title":"StringUtil"},{"location":"javascript/new-api_core/#escapehtmlstr-string-string","text":"Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt;","title":"escapeHTML(str: string): string"},{"location":"javascript/new-api_core/#escaperegexpstr-string-string","text":"Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions.","title":"escapeRegExp(str: string): string"},{"location":"javascript/new-api_core/#lcfirststr-string-string","text":"Makes a string's first character lowercase.","title":"lcfirst(str: string): string"},{"location":"javascript/new-api_core/#ucfirststr-string-string","text":"Makes a string's first character uppercase.","title":"ucfirst(str: string): string"},{"location":"javascript/new-api_core/#unescapehtmlstr-string-string","text":"Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"unescapeHTML(str: string): string"},{"location":"javascript/new-api_data-structures/","text":"Data Structures - JavaScript API # Introduction # JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11. Dictionary # Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object. set(key: string, value: any) # Adds or updates an item using the provided key. Numeric keys will be converted into strings. delete(key: string) # Removes an item from the collection. has(key: string): boolean # Returns true if the key is contained in the collection. get(key: string): any # Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence. forEach(callback: (value: any, key: string) => void) # Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key. size: number # This read-only property counts the number of items in the collection. List # Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array. add(value: any) # Adds a value to the list. If the value is already part of the list, this method will silently abort. clear() # Resets the collection. delete(value: any): boolean # Attempts to remove a value from the list, it returns true if the value has been part of the list. forEach(callback: (value: any) => void) # Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value. has(value: any): boolean # Returns true if the provided value is part of this list. size: number # This read-only property counts the number of items in the list. ObjectMap # This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects. set(key: Object, value: Object) # Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten. delete(key: Object) # Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection. has(key: Object): boolean # Returns true if there is a value for the provided key in this collection. get(key: Object): Object | undefined # Retrieves the value of the provided key, or undefined if the key was not found.","title":"Data Structures"},{"location":"javascript/new-api_data-structures/#data-structures-javascript-api","text":"","title":"Data Structures - JavaScript API"},{"location":"javascript/new-api_data-structures/#introduction","text":"JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11.","title":"Introduction"},{"location":"javascript/new-api_data-structures/#dictionary","text":"Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object.","title":"Dictionary"},{"location":"javascript/new-api_data-structures/#setkey-string-value-any","text":"Adds or updates an item using the provided key. Numeric keys will be converted into strings.","title":"set(key: string, value: any)"},{"location":"javascript/new-api_data-structures/#deletekey-string","text":"Removes an item from the collection.","title":"delete(key: string)"},{"location":"javascript/new-api_data-structures/#haskey-string-boolean","text":"Returns true if the key is contained in the collection.","title":"has(key: string): boolean"},{"location":"javascript/new-api_data-structures/#getkey-string-any","text":"Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence.","title":"get(key: string): any"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-key-string-void","text":"Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key.","title":"forEach(callback: (value: any, key: string) =&gt; void)"},{"location":"javascript/new-api_data-structures/#size-number","text":"This read-only property counts the number of items in the collection.","title":"size: number"},{"location":"javascript/new-api_data-structures/#list","text":"Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array.","title":"List"},{"location":"javascript/new-api_data-structures/#addvalue-any","text":"Adds a value to the list. If the value is already part of the list, this method will silently abort.","title":"add(value: any)"},{"location":"javascript/new-api_data-structures/#clear","text":"Resets the collection.","title":"clear()"},{"location":"javascript/new-api_data-structures/#deletevalue-any-boolean","text":"Attempts to remove a value from the list, it returns true if the value has been part of the list.","title":"delete(value: any): boolean"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-void","text":"Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value.","title":"forEach(callback: (value: any) =&gt; void)"},{"location":"javascript/new-api_data-structures/#hasvalue-any-boolean","text":"Returns true if the provided value is part of this list.","title":"has(value: any): boolean"},{"location":"javascript/new-api_data-structures/#size-number_1","text":"This read-only property counts the number of items in the list.","title":"size: number"},{"location":"javascript/new-api_data-structures/#objectmap","text":"This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects.","title":"ObjectMap"},{"location":"javascript/new-api_data-structures/#setkey-object-value-object","text":"Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten.","title":"set(key: Object, value: Object)"},{"location":"javascript/new-api_data-structures/#deletekey-object","text":"Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection.","title":"delete(key: Object)"},{"location":"javascript/new-api_data-structures/#haskey-object-boolean","text":"Returns true if there is a value for the provided key in this collection.","title":"has(key: Object): boolean"},{"location":"javascript/new-api_data-structures/#getkey-object-object-undefined","text":"Retrieves the value of the provided key, or undefined if the key was not found.","title":"get(key: Object): Object | undefined"},{"location":"javascript/new-api_dialogs/","text":"Dialogs - JavaScript API # Introduction # Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs. _dialogSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; }); id: string # The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id. source: any # There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog. options: Object # All configuration options and callbacks are handled through this object. options.backdropCloseOnClick: boolean # Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false . options.closable: boolean # Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only. options.closeButtonLabel: string # Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button. options.closeConfirmMessage: string # Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user. options.title: string # Defaults to \"\" . The phrase that is displayed in the dialog title. options.onBeforeClose: (id: string) => void # Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented. options.onClose: (id: string) => void # Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point. options.onShow: (content: Element) => void # Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet. setTitle(id: string | Object, title: string) # Sets the title of a dialog. setCallback(id: string | Object, key: string, value: (data: any) => void | null) # Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow . rebuild(id: string | Object) # Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback. close(id: string | Object) # Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation. getDialog(id: string | Object): Object # This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element. isOpen(id: string | Object): boolean # Returns true if the dialog exists and is open.","title":"Dialogs"},{"location":"javascript/new-api_dialogs/#dialogs-javascript-api","text":"","title":"Dialogs - JavaScript API"},{"location":"javascript/new-api_dialogs/#introduction","text":"Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs.","title":"Introduction"},{"location":"javascript/new-api_dialogs/#_dialogsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; });","title":"_dialogSetup()"},{"location":"javascript/new-api_dialogs/#id-string","text":"The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id.","title":"id: string"},{"location":"javascript/new-api_dialogs/#source-any","text":"There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog.","title":"source: any"},{"location":"javascript/new-api_dialogs/#options-object","text":"All configuration options and callbacks are handled through this object.","title":"options: Object"},{"location":"javascript/new-api_dialogs/#optionsbackdropcloseonclick-boolean","text":"Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false .","title":"options.backdropCloseOnClick: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosable-boolean","text":"Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only.","title":"options.closable: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosebuttonlabel-string","text":"Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button.","title":"options.closeButtonLabel: string"},{"location":"javascript/new-api_dialogs/#optionscloseconfirmmessage-string","text":"Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user.","title":"options.closeConfirmMessage: string"},{"location":"javascript/new-api_dialogs/#optionstitle-string","text":"Defaults to \"\" . The phrase that is displayed in the dialog title.","title":"options.title: string"},{"location":"javascript/new-api_dialogs/#optionsonbeforeclose-id-string-void","text":"Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented.","title":"options.onBeforeClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonclose-id-string-void","text":"Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point.","title":"options.onClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonshow-content-element-void","text":"Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet.","title":"options.onShow: (content: Element) =&gt; void"},{"location":"javascript/new-api_dialogs/#settitleid-string-object-title-string","text":"Sets the title of a dialog.","title":"setTitle(id: string | Object, title: string)"},{"location":"javascript/new-api_dialogs/#setcallbackid-string-object-key-string-value-data-any-void-null","text":"Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow .","title":"setCallback(id: string | Object, key: string, value: (data: any) =&gt; void | null)"},{"location":"javascript/new-api_dialogs/#rebuildid-string-object","text":"Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback.","title":"rebuild(id: string | Object)"},{"location":"javascript/new-api_dialogs/#closeid-string-object","text":"Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation.","title":"close(id: string | Object)"},{"location":"javascript/new-api_dialogs/#getdialogid-string-object-object","text":"This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element.","title":"getDialog(id: string | Object): Object"},{"location":"javascript/new-api_dialogs/#isopenid-string-object-boolean","text":"Returns true if the dialog exists and is open.","title":"isOpen(id: string | Object): boolean"},{"location":"javascript/new-api_dom/","text":"Working with the DOM - JavaScript API # Helper Functions # There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports. Dom/Util # createFragmentFromHtml(html: string): DocumentFragment # Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes. identify(element: Element): string # Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead. outerHeight(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom . outerWidth(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right . outerDimensions(element: Element): { height: number, width: number } # Computes the outer dimensions of an element including its margins. offset(element: Element): { top: number, left: number } # Computes the element's offset relative to the top left corner of the document. setInnerHtml(element: Element, innerHtml: string) # Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance. contains(element: Element, child: Element): boolean # Evaluates if element is a direct or indirect parent element of child . unwrapChildNodes(element: Element) # Moves all child nodes out of element while maintaining their order, then removes element from the document. Dom/ChangeListener # This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"DOM"},{"location":"javascript/new-api_dom/#working-with-the-dom-javascript-api","text":"","title":"Working with the DOM - JavaScript API"},{"location":"javascript/new-api_dom/#helper-functions","text":"There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports.","title":"Helper Functions"},{"location":"javascript/new-api_dom/#domutil","text":"","title":"Dom/Util"},{"location":"javascript/new-api_dom/#createfragmentfromhtmlhtml-string-documentfragment","text":"Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes.","title":"createFragmentFromHtml(html: string): DocumentFragment"},{"location":"javascript/new-api_dom/#identifyelement-element-string","text":"Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead.","title":"identify(element: Element): string"},{"location":"javascript/new-api_dom/#outerheightelement-element-styles-cssstyledeclaration-number","text":"Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom .","title":"outerHeight(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerwidthelement-element-styles-cssstyledeclaration-number","text":"Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right .","title":"outerWidth(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerdimensionselement-element-height-number-width-number","text":"Computes the outer dimensions of an element including its margins.","title":"outerDimensions(element: Element): { height: number, width: number }"},{"location":"javascript/new-api_dom/#offsetelement-element-top-number-left-number","text":"Computes the element's offset relative to the top left corner of the document.","title":"offset(element: Element): { top: number, left: number }"},{"location":"javascript/new-api_dom/#setinnerhtmlelement-element-innerhtml-string","text":"Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance.","title":"setInnerHtml(element: Element, innerHtml: string)"},{"location":"javascript/new-api_dom/#containselement-element-child-element-boolean","text":"Evaluates if element is a direct or indirect parent element of child .","title":"contains(element: Element, child: Element): boolean"},{"location":"javascript/new-api_dom/#unwrapchildnodeselement-element","text":"Moves all child nodes out of element while maintaining their order, then removes element from the document.","title":"unwrapChildNodes(element: Element)"},{"location":"javascript/new-api_dom/#domchangelistener","text":"This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"Dom/ChangeListener"},{"location":"javascript/new-api_events/","text":"Event Handling - JavaScript API # EventKey # This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); }); ArrowDown(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2193 key. ArrowLeft(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2190 key. ArrowRight(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2192 key. ArrowUp(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2191 key. Comma(event: KeyboardEvent): boolean # Returns true if the user has pressed the , key. Enter(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b2 key. Escape(event: KeyboardEvent): boolean # Returns true if the user has pressed the Esc key. Tab(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b9 key. EventHandler # A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time. Identifiying Events with the Developer Tools # The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo add(identifier: string, action: string, callback: (data: Object) => void): string # Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method. fire(identifier: string, action: string, data?: Object) # Triggers an event using an optional data object that is passed to each listener by reference. remove(identifier: string, action: string, uuid: string) # Removes a previously registered event listener using the UUID returned by add() . removeAll(identifier: string, action: string) # Removes all event listeners registered for the provided identifier and action . removeAllBySuffix(identifier: string, suffix: string) # Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"Event Handling"},{"location":"javascript/new-api_events/#event-handling-javascript-api","text":"","title":"Event Handling - JavaScript API"},{"location":"javascript/new-api_events/#eventkey","text":"This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); });","title":"EventKey"},{"location":"javascript/new-api_events/#arrowdownevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2193 key.","title":"ArrowDown(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowleftevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2190 key.","title":"ArrowLeft(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowrightevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2192 key.","title":"ArrowRight(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowupevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2191 key.","title":"ArrowUp(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#commaevent-keyboardevent-boolean","text":"Returns true if the user has pressed the , key.","title":"Comma(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#enterevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b2 key.","title":"Enter(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#escapeevent-keyboardevent-boolean","text":"Returns true if the user has pressed the Esc key.","title":"Escape(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#tabevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b9 key.","title":"Tab(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#eventhandler","text":"A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time.","title":"EventHandler"},{"location":"javascript/new-api_events/#identifiying-events-with-the-developer-tools","text":"The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo","title":"Identifiying Events with the Developer Tools"},{"location":"javascript/new-api_events/#addidentifier-string-action-string-callback-data-object-void-string","text":"Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method.","title":"add(identifier: string, action: string, callback: (data: Object) =&gt; void): string"},{"location":"javascript/new-api_events/#fireidentifier-string-action-string-data-object","text":"Triggers an event using an optional data object that is passed to each listener by reference.","title":"fire(identifier: string, action: string, data?: Object)"},{"location":"javascript/new-api_events/#removeidentifier-string-action-string-uuid-string","text":"Removes a previously registered event listener using the UUID returned by add() .","title":"remove(identifier: string, action: string, uuid: string)"},{"location":"javascript/new-api_events/#removeallidentifier-string-action-string","text":"Removes all event listeners registered for the provided identifier and action .","title":"removeAll(identifier: string, action: string)"},{"location":"javascript/new-api_events/#removeallbysuffixidentifier-string-suffix-string","text":"Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"removeAllBySuffix(identifier: string, suffix: string)"},{"location":"javascript/new-api_ui/","text":"User Interface - JavaScript API # Ui/Alignment # Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements. set(element: Element, referenceElement: Element, options: Object) # Calculates and sets the alignment of the element element . verticalOffset: number # Defaults to 0 . Creates a gap between the element and the reference element, in pixels. pointer: boolean # Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer . pointerOffset: number # Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center. pointerClassNames: string[] # Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element. refDimensionsElement: Element # Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions. horizontal: string # This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used. vertical: string # Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used. allowFlip: string # The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints. Ui/CloseOverlay # Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); }); add(identifier: string, callback: () => void) # Adds a callback that will be invoked when the user clicks anywhere else. Ui/Confirmation # Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); }); show(options: Object) # Displays a dialog overlay with actions buttons to confirm or reject the dialog. cancel: (parameters: Object) => void # Defaults to null . Callback that is invoked when the dialog was rejected. confirm: (parameters: Object) => void # Defaults to null . Callback that is invoked when the user has confirmed the dialog. message: string # Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled. messageIsHtml # Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML. parameters: Object # Optional list of parameter options that will be passed to the cancel() and confirm() callbacks. template: string # An optional HTML template that will be inserted into the dialog content area, but after the message section. Ui/Notification # Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); }); show(message: string, callback?: () => void, cssClassName?: string) # Shows the notification and executes the callback after 2 seconds.","title":"User Interface"},{"location":"javascript/new-api_ui/#user-interface-javascript-api","text":"","title":"User Interface - JavaScript API"},{"location":"javascript/new-api_ui/#uialignment","text":"Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements.","title":"Ui/Alignment"},{"location":"javascript/new-api_ui/#setelement-element-referenceelement-element-options-object","text":"Calculates and sets the alignment of the element element .","title":"set(element: Element, referenceElement: Element, options: Object)"},{"location":"javascript/new-api_ui/#verticaloffset-number","text":"Defaults to 0 . Creates a gap between the element and the reference element, in pixels.","title":"verticalOffset: number"},{"location":"javascript/new-api_ui/#pointer-boolean","text":"Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer .","title":"pointer: boolean"},{"location":"javascript/new-api_ui/#pointeroffset-number","text":"Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center.","title":"pointerOffset: number"},{"location":"javascript/new-api_ui/#pointerclassnames-string","text":"Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element.","title":"pointerClassNames: string[]"},{"location":"javascript/new-api_ui/#refdimensionselement-element","text":"Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions.","title":"refDimensionsElement: Element"},{"location":"javascript/new-api_ui/#horizontal-string","text":"This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used.","title":"horizontal: string"},{"location":"javascript/new-api_ui/#vertical-string","text":"Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used.","title":"vertical: string"},{"location":"javascript/new-api_ui/#allowflip-string","text":"The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints.","title":"allowFlip: string"},{"location":"javascript/new-api_ui/#uicloseoverlay","text":"Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); });","title":"Ui/CloseOverlay"},{"location":"javascript/new-api_ui/#addidentifier-string-callback-void","text":"Adds a callback that will be invoked when the user clicks anywhere else.","title":"add(identifier: string, callback: () =&gt; void)"},{"location":"javascript/new-api_ui/#uiconfirmation","text":"Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); });","title":"Ui/Confirmation"},{"location":"javascript/new-api_ui/#showoptions-object","text":"Displays a dialog overlay with actions buttons to confirm or reject the dialog.","title":"show(options: Object)"},{"location":"javascript/new-api_ui/#cancel-parameters-object-void","text":"Defaults to null . Callback that is invoked when the dialog was rejected.","title":"cancel: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#confirm-parameters-object-void","text":"Defaults to null . Callback that is invoked when the user has confirmed the dialog.","title":"confirm: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#message-string","text":"Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled.","title":"message: string"},{"location":"javascript/new-api_ui/#messageishtml","text":"Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML.","title":"messageIsHtml"},{"location":"javascript/new-api_ui/#parameters-object","text":"Optional list of parameter options that will be passed to the cancel() and confirm() callbacks.","title":"parameters: Object"},{"location":"javascript/new-api_ui/#template-string","text":"An optional HTML template that will be inserted into the dialog content area, but after the message section.","title":"template: string"},{"location":"javascript/new-api_ui/#uinotification","text":"Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); });","title":"Ui/Notification"},{"location":"javascript/new-api_ui/#showmessage-string-callback-void-cssclassname-string","text":"Shows the notification and executes the callback after 2 seconds.","title":"show(message: string, callback?: () =&gt; void, cssClassName?: string)"},{"location":"javascript/new-api_writing-a-module/","text":"Writing a Module - JavaScript API # Introduction # The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start. Defining a Module # The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; }); Loading a Module # Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script > Module Aliases # Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Writing a module"},{"location":"javascript/new-api_writing-a-module/#writing-a-module-javascript-api","text":"","title":"Writing a Module - JavaScript API"},{"location":"javascript/new-api_writing-a-module/#introduction","text":"The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start.","title":"Introduction"},{"location":"javascript/new-api_writing-a-module/#defining-a-module","text":"The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; });","title":"Defining a Module"},{"location":"javascript/new-api_writing-a-module/#loading-a-module","text":"Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script >","title":"Loading a Module"},{"location":"javascript/new-api_writing-a-module/#module-aliases","text":"Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Module Aliases"},{"location":"migration/wcf21/css/","text":"WCF 2.1.x - CSS # The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"CSS"},{"location":"migration/wcf21/css/#wcf-21x-css","text":"The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"WCF 2.1.x - CSS"},{"location":"migration/wcf21/package/","text":"WCF 2.1.x - Package Components # package.xml # Short Instructions # Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability. Example # <instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions> Exceptions # These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar acpMenu.xml # Renamed Categories # The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application Submenu Items # Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item. Example # <!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem> Common Icon Names # You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload box.xml # The box PIP has been added. cronjob.xml # Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete. eventListener.xml # Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete. menu.xml # The menu PIP has been added. menuItem.xml # The menuItem PIP has been added. objectType.xml # The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported. option.xml # The module.display category has been renamed into module.customization . page.xml # The page PIP has been added. pageMenu.xml # The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"Package Components"},{"location":"migration/wcf21/package/#wcf-21x-package-components","text":"","title":"WCF 2.1.x - Package Components"},{"location":"migration/wcf21/package/#packagexml","text":"","title":"package.xml"},{"location":"migration/wcf21/package/#short-instructions","text":"Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability.","title":"Short Instructions"},{"location":"migration/wcf21/package/#example","text":"<instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions>","title":"Example"},{"location":"migration/wcf21/package/#exceptions","text":"These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar","title":"Exceptions"},{"location":"migration/wcf21/package/#acpmenuxml","text":"","title":"acpMenu.xml"},{"location":"migration/wcf21/package/#renamed-categories","text":"The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application","title":"Renamed Categories"},{"location":"migration/wcf21/package/#submenu-items","text":"Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item.","title":"Submenu Items"},{"location":"migration/wcf21/package/#example_1","text":"<!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem>","title":"Example"},{"location":"migration/wcf21/package/#common-icon-names","text":"You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload","title":"Common Icon Names"},{"location":"migration/wcf21/package/#boxxml","text":"The box PIP has been added.","title":"box.xml"},{"location":"migration/wcf21/package/#cronjobxml","text":"Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete.","title":"cronjob.xml"},{"location":"migration/wcf21/package/#eventlistenerxml","text":"Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete.","title":"eventListener.xml"},{"location":"migration/wcf21/package/#menuxml","text":"The menu PIP has been added.","title":"menu.xml"},{"location":"migration/wcf21/package/#menuitemxml","text":"The menuItem PIP has been added.","title":"menuItem.xml"},{"location":"migration/wcf21/package/#objecttypexml","text":"The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported.","title":"objectType.xml"},{"location":"migration/wcf21/package/#optionxml","text":"The module.display category has been renamed into module.customization .","title":"option.xml"},{"location":"migration/wcf21/package/#pagexml","text":"The page PIP has been added.","title":"page.xml"},{"location":"migration/wcf21/package/#pagemenuxml","text":"The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"pageMenu.xml"},{"location":"migration/wcf21/php/","text":"WCF 2.1.x - PHP # Message Processing # WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done. Input Processing for Storage # The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message. Embedded Objects # Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); } Rendering the Message # The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml (); Simplified Output # At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 ); Plaintext Output # The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 ); Rebuilding Data # Converting from BBCode # Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml (); Extracting Embedded Objects # The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here Breadcrumbs / Page Location # Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object ); Pages and Forms # The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work. Search # ISearchableObjectType # Added the setLocation() method that is used to set the current page location based on the search result. SearchIndexManager # The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"PHP API"},{"location":"migration/wcf21/php/#wcf-21x-php","text":"","title":"WCF 2.1.x - PHP"},{"location":"migration/wcf21/php/#message-processing","text":"WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done.","title":"Message Processing"},{"location":"migration/wcf21/php/#input-processing-for-storage","text":"The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message.","title":"Input Processing for Storage"},{"location":"migration/wcf21/php/#embedded-objects","text":"Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); }","title":"Embedded Objects"},{"location":"migration/wcf21/php/#rendering-the-message","text":"The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml ();","title":"Rendering the Message"},{"location":"migration/wcf21/php/#simplified-output","text":"At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 );","title":"Simplified Output"},{"location":"migration/wcf21/php/#plaintext-output","text":"The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 );","title":"Plaintext Output"},{"location":"migration/wcf21/php/#rebuilding-data","text":"","title":"Rebuilding Data"},{"location":"migration/wcf21/php/#converting-from-bbcode","text":"Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml ();","title":"Converting from BBCode"},{"location":"migration/wcf21/php/#extracting-embedded-objects","text":"The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here","title":"Extracting Embedded Objects"},{"location":"migration/wcf21/php/#breadcrumbs-page-location","text":"Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object );","title":"Breadcrumbs / Page Location"},{"location":"migration/wcf21/php/#pages-and-forms","text":"The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work.","title":"Pages and Forms"},{"location":"migration/wcf21/php/#search","text":"","title":"Search"},{"location":"migration/wcf21/php/#isearchableobjecttype","text":"Added the setLocation() method that is used to set the current page location based on the search result.","title":"ISearchableObjectType"},{"location":"migration/wcf21/php/#searchindexmanager","text":"The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"SearchIndexManager"},{"location":"migration/wcf21/templates/","text":"WCF 2.1.x - Templates # Page Layout # The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information. Sidebars # Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section > Forms # The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing. Icons # The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" > Changed Icon Names # Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4. Changed Classes # .dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList Removed Elements and Classes # <nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible. Simple Example # The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' } Full Example # { * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' } Content Header # There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible. Recommended Approach # { * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture } Alternative # { capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Templates"},{"location":"migration/wcf21/templates/#wcf-21x-templates","text":"","title":"WCF 2.1.x - Templates"},{"location":"migration/wcf21/templates/#page-layout","text":"The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information.","title":"Page Layout"},{"location":"migration/wcf21/templates/#sidebars","text":"Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section >","title":"Sidebars"},{"location":"migration/wcf21/templates/#forms","text":"The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing.","title":"Forms"},{"location":"migration/wcf21/templates/#icons","text":"The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" >","title":"Icons"},{"location":"migration/wcf21/templates/#changed-icon-names","text":"Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4.","title":"Changed Icon Names"},{"location":"migration/wcf21/templates/#changed-classes","text":".dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList","title":"Changed Classes"},{"location":"migration/wcf21/templates/#removed-elements-and-classes","text":"<nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible.","title":"Removed Elements and Classes"},{"location":"migration/wcf21/templates/#simple-example","text":"The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' }","title":"Simple Example"},{"location":"migration/wcf21/templates/#full-example","text":"{ * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' }","title":"Full Example"},{"location":"migration/wcf21/templates/#content-header","text":"There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible.","title":"Content Header"},{"location":"migration/wcf21/templates/#recommended-approach","text":"{ * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture }","title":"Recommended Approach"},{"location":"migration/wcf21/templates/#alternative","text":"{ capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Alternative"},{"location":"migration/wsc30/css/","text":"Migrating from WSC 3.0 - CSS # New Style Variables # The new style variables are only applied to styles that have the compatibility set to WSC 3.1 wcfContentContainer # The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color wcfEditorButton # These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons Color Variables in alert.scss # The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"CSS"},{"location":"migration/wsc30/css/#migrating-from-wsc-30-css","text":"","title":"Migrating from WSC 3.0 - CSS"},{"location":"migration/wsc30/css/#new-style-variables","text":"The new style variables are only applied to styles that have the compatibility set to WSC 3.1","title":"New Style Variables"},{"location":"migration/wsc30/css/#wcfcontentcontainer","text":"The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color","title":"wcfContentContainer"},{"location":"migration/wsc30/css/#wcfeditorbutton","text":"These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons","title":"wcfEditorButton"},{"location":"migration/wsc30/css/#color-variables-in-alertscss","text":"The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"Color Variables in alert.scss"},{"location":"migration/wsc30/javascript/","text":"Migrating from WSC 3.0 - JavaScript # Accelerated Guest View / Tiny Builds # The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever. Code Templates for Tiny Builds # The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now. Legacy JavaScript # if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); } require.js Modules # define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; }); Including tinified builds through {js} # The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state. Real Error Messages for AJAX Responses # The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms. Example Code # define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; }); Simplified Form Submit in Dialogs # Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field. Helper Function for Inline Error Messages # Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef) Example Code # require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"JavaScript API"},{"location":"migration/wsc30/javascript/#migrating-from-wsc-30-javascript","text":"","title":"Migrating from WSC 3.0 - JavaScript"},{"location":"migration/wsc30/javascript/#accelerated-guest-view-tiny-builds","text":"The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever.","title":"Accelerated Guest View / Tiny Builds"},{"location":"migration/wsc30/javascript/#code-templates-for-tiny-builds","text":"The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now.","title":"Code Templates for Tiny Builds"},{"location":"migration/wsc30/javascript/#legacy-javascript","text":"if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); }","title":"Legacy JavaScript"},{"location":"migration/wsc30/javascript/#requirejs-modules","text":"define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; });","title":"require.js Modules"},{"location":"migration/wsc30/javascript/#including-tinified-builds-through-js","text":"The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state.","title":"Including tinified builds through {js}"},{"location":"migration/wsc30/javascript/#real-error-messages-for-ajax-responses","text":"The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms.","title":"Real Error Messages for AJAX Responses"},{"location":"migration/wsc30/javascript/#example-code","text":"define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; });","title":"Example Code"},{"location":"migration/wsc30/javascript/#simplified-form-submit-in-dialogs","text":"Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field.","title":"Simplified Form Submit in Dialogs"},{"location":"migration/wsc30/javascript/#helper-function-for-inline-error-messages","text":"Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef)","title":"Helper Function for Inline Error Messages"},{"location":"migration/wsc30/javascript/#example-code_1","text":"require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"Example Code"},{"location":"migration/wsc30/package/","text":"Migrating from WSC 3.0 - Package Components # Cronjob Scheduler uses Server Timezone # The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel. Exclude Pages from becoming a Landing Page # Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml . Example Code # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data> New Package Installation Plugin for Media Providers # Please refer to the documentation of the mediaProvider.xml to learn more. Limited Forward-Compatibility for Plugins # Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Package Components"},{"location":"migration/wsc30/package/#migrating-from-wsc-30-package-components","text":"","title":"Migrating from WSC 3.0 - Package Components"},{"location":"migration/wsc30/package/#cronjob-scheduler-uses-server-timezone","text":"The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel.","title":"Cronjob Scheduler uses Server Timezone"},{"location":"migration/wsc30/package/#exclude-pages-from-becoming-a-landing-page","text":"Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml .","title":"Exclude Pages from becoming a Landing Page"},{"location":"migration/wsc30/package/#example-code","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data>","title":"Example Code"},{"location":"migration/wsc30/package/#new-package-installation-plugin-for-media-providers","text":"Please refer to the documentation of the mediaProvider.xml to learn more.","title":"New Package Installation Plugin for Media Providers"},{"location":"migration/wsc30/package/#limited-forward-compatibility-for-plugins","text":"Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Limited Forward-Compatibility for Plugins"},{"location":"migration/wsc30/php/","text":"Migrating from WSC 3.0 - PHP # Approval-System for Comments # Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; } Raw HTML in User Activity Events # User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; } Permission to View Likes of an Object # Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } } Developer Tools: Sync Feature # The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } } Media Providers # Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation. Example Implementation # mediaProvider.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data> PHP Callback # The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } } Re-Evaluate HTML Messages # You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously. Example Usage # <? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"PHP API"},{"location":"migration/wsc30/php/#migrating-from-wsc-30-php","text":"","title":"Migrating from WSC 3.0 - PHP"},{"location":"migration/wsc30/php/#approval-system-for-comments","text":"Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; }","title":"Approval-System for Comments"},{"location":"migration/wsc30/php/#raw-html-in-user-activity-events","text":"User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; }","title":"Raw HTML in User Activity Events"},{"location":"migration/wsc30/php/#permission-to-view-likes-of-an-object","text":"Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } }","title":"Permission to View Likes of an Object"},{"location":"migration/wsc30/php/#developer-tools-sync-feature","text":"The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } }","title":"Developer Tools: Sync Feature"},{"location":"migration/wsc30/php/#media-providers","text":"Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation.","title":"Media Providers"},{"location":"migration/wsc30/php/#example-implementation","text":"","title":"Example Implementation"},{"location":"migration/wsc30/php/#mediaproviderxml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data>","title":"mediaProvider.xml"},{"location":"migration/wsc30/php/#php-callback","text":"The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } }","title":"PHP Callback"},{"location":"migration/wsc30/php/#re-evaluate-html-messages","text":"You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously.","title":"Re-Evaluate HTML Messages"},{"location":"migration/wsc30/php/#example-usage","text":"<? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"Example Usage"},{"location":"migration/wsc30/templates/","text":"Migrating from WSC 3.0 - Templates # Comment-System Overhaul # Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments. Adding Comments # Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul > Redesigned ACP User List # Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li > Sidebar Toogle-Buttons on Mobile Device # You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Templates"},{"location":"migration/wsc30/templates/#migrating-from-wsc-30-templates","text":"","title":"Migrating from WSC 3.0 - Templates"},{"location":"migration/wsc30/templates/#comment-system-overhaul","text":"Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments.","title":"Comment-System Overhaul"},{"location":"migration/wsc30/templates/#adding-comments","text":"Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul >","title":"Adding Comments"},{"location":"migration/wsc30/templates/#redesigned-acp-user-list","text":"Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li >","title":"Redesigned ACP User List"},{"location":"migration/wsc30/templates/#sidebar-toogle-buttons-on-mobile-device","text":"You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Sidebar Toogle-Buttons on Mobile Device"},{"location":"migration/wsc31/form-builder/","text":"Migrating from WSC 3.1 - Form Builder # Example: Two Text Form Fields # As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#migrating-from-wsc-31-form-builder","text":"","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#example-two-text-form-fields","text":"As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Example: Two Text Form Fields"},{"location":"migration/wsc31/like/","text":"Migrating from WSC 3.1 - Like System # Introduction # With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments. Limitations if no adjustments are made to the existing code # If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes Migration # Notifications # Mark notification as compatible # Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again. Language Variables # Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # {prefix}.like.title Reaction to a {objectName} {prefix}.like.title.stacked {#$count} users reacted to your {objectName} {prefix}.like.message {@$author->getAnchorTag()} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} others{/if} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{objectTypeName}.like.notification.like Notify me when someone reacted to my {objectName} German # {prefix}.like.title Reaktion auf einen {objectName} {prefix}.like.title.stacked {#$count} Benutzern haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert {prefix}.like.message {@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{object_type_name}.like.notification.like Jemandem hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert Recent Activity # To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}. German # wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert. Comments # If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type> Forward Compatibility # So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#migrating-from-wsc-31-like-system","text":"","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#introduction","text":"With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments.","title":"Introduction"},{"location":"migration/wsc31/like/#limitations-if-no-adjustments-are-made-to-the-existing-code","text":"If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes","title":"Limitations if no adjustments are made to the existing code"},{"location":"migration/wsc31/like/#migration","text":"","title":"Migration"},{"location":"migration/wsc31/like/#notifications","text":"","title":"Notifications"},{"location":"migration/wsc31/like/#mark-notification-as-compatible","text":"Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again.","title":"Mark notification as compatible"},{"location":"migration/wsc31/like/#language-variables","text":"Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Language Variables"},{"location":"migration/wsc31/like/#recent-activity","text":"To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Recent Activity"},{"location":"migration/wsc31/like/#english_1","text":"wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}.","title":"English"},{"location":"migration/wsc31/like/#german_1","text":"wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert.","title":"German"},{"location":"migration/wsc31/like/#comments","text":"If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type>","title":"Comments"},{"location":"migration/wsc31/like/#forward-compatibility","text":"So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Forward Compatibility"},{"location":"migration/wsc31/php/","text":"Migrating from WSC 3.1 - PHP # Form Builder # WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway. Like System # WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here . User Content Providers # User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects. PHP Class # First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } } Object Type # Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type: nicevalue # Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first. hidden # Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically! requiredobjecttype # Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important. PHP Database API # WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP API"},{"location":"migration/wsc31/php/#migrating-from-wsc-31-php","text":"","title":"Migrating from WSC 3.1 - PHP"},{"location":"migration/wsc31/php/#form-builder","text":"WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway.","title":"Form Builder"},{"location":"migration/wsc31/php/#like-system","text":"WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here .","title":"Like System"},{"location":"migration/wsc31/php/#user-content-providers","text":"User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects.","title":"User Content Providers"},{"location":"migration/wsc31/php/#php-class","text":"First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } }","title":"PHP Class"},{"location":"migration/wsc31/php/#object-type","text":"Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type:","title":"Object Type"},{"location":"migration/wsc31/php/#nicevalue","text":"Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first.","title":"nicevalue"},{"location":"migration/wsc31/php/#hidden","text":"Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically!","title":"hidden"},{"location":"migration/wsc31/php/#requiredobjecttype","text":"Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important.","title":"requiredobjecttype"},{"location":"migration/wsc31/php/#php-database-api","text":"WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP Database API"},{"location":"migration/wsc52/libraries/","text":"Migrating from WSC 5.2 - Third Party Libraries # SCSS Compiler # WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 . Guzzle # WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Third Party Libraries"},{"location":"migration/wsc52/libraries/#migrating-from-wsc-52-third-party-libraries","text":"","title":"Migrating from WSC 5.2 - Third Party Libraries"},{"location":"migration/wsc52/libraries/#scss-compiler","text":"WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 .","title":"SCSS Compiler"},{"location":"migration/wsc52/libraries/#guzzle","text":"WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Guzzle"},{"location":"migration/wsc52/php/","text":"Migrating from WSC 5.2 - PHP # Comments # The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager . Event Listeners # The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener . Email Activation # Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() . *AddForm # WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]); User Generated Links # It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available. Resource Management When Scaling Images # It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details. Toggle for Accelerated Mobile Pages (AMP) # Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"PHP API"},{"location":"migration/wsc52/php/#migrating-from-wsc-52-php","text":"","title":"Migrating from WSC 5.2 - PHP"},{"location":"migration/wsc52/php/#comments","text":"The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager .","title":"Comments"},{"location":"migration/wsc52/php/#event-listeners","text":"The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener .","title":"Event Listeners"},{"location":"migration/wsc52/php/#email-activation","text":"Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() .","title":"Email Activation"},{"location":"migration/wsc52/php/#addform","text":"WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]);","title":"*AddForm"},{"location":"migration/wsc52/php/#user-generated-links","text":"It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available.","title":"User Generated Links"},{"location":"migration/wsc52/php/#resource-management-when-scaling-images","text":"It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details.","title":"Resource Management When Scaling Images"},{"location":"migration/wsc52/php/#toggle-for-accelerated-mobile-pages-amp","text":"Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"Toggle for Accelerated Mobile Pages (AMP)"},{"location":"migration/wsc52/templates/","text":"Migrating from WSC 5.2 - Templates and Languages # {jslang} # Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script> Template Plugins # The {anchor} , {plural} , and {user} template plugins have been added. Notification Language Items # In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language } Popovers # Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Templates and Languages"},{"location":"migration/wsc52/templates/#migrating-from-wsc-52-templates-and-languages","text":"","title":"Migrating from WSC 5.2 - Templates and Languages"},{"location":"migration/wsc52/templates/#jslang","text":"Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script>","title":"{jslang}"},{"location":"migration/wsc52/templates/#template-plugins","text":"The {anchor} , {plural} , and {user} template plugins have been added.","title":"Template Plugins"},{"location":"migration/wsc52/templates/#notification-language-items","text":"In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language }","title":"Notification Language Items"},{"location":"migration/wsc52/templates/#popovers","text":"Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Popovers"},{"location":"migration/wsc53/javascript/","text":"Migrating from WSC 5.3 - JavaScript # WCF_CLICK_EVENT # For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev )); WCF.Action.Delete and WCF.Action.Toggle # WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" {* *}data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> {* \u2026 *} </tr> </thead> <tbody> {foreach from=$objects item=foo} <tr class=\"jsObjectActionObject\" data-object-id=\"{@$foo->getObjectID()}\"> <td class=\"columnIcon\"> {objectAction action=\"toggle\" isDisabled=$foo->isDisabled} {objectAction action=\"delete\" objectTitle=$foo->getTitle()} {* \u2026 *} </td> {* \u2026 *} </tr> {/foreach} </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 . WCF.Table.EmptyTableHandler # When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"JavaScript"},{"location":"migration/wsc53/javascript/#migrating-from-wsc-53-javascript","text":"","title":"Migrating from WSC 5.3 - JavaScript"},{"location":"migration/wsc53/javascript/#wcf_click_event","text":"For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev ));","title":"WCF_CLICK_EVENT"},{"location":"migration/wsc53/javascript/#wcfactiondelete-and-wcfactiontoggle","text":"WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" {* *}data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> {* \u2026 *} </tr> </thead> <tbody> {foreach from=$objects item=foo} <tr class=\"jsObjectActionObject\" data-object-id=\"{@$foo->getObjectID()}\"> <td class=\"columnIcon\"> {objectAction action=\"toggle\" isDisabled=$foo->isDisabled} {objectAction action=\"delete\" objectTitle=$foo->getTitle()} {* \u2026 *} </td> {* \u2026 *} </tr> {/foreach} </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 .","title":"WCF.Action.Delete and WCF.Action.Toggle"},{"location":"migration/wsc53/javascript/#wcftableemptytablehandler","text":"When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"WCF.Table.EmptyTableHandler"},{"location":"migration/wsc53/libraries/","text":"Migrating from WSC 5.3 - Third Party Libraries # Guzzle # The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7. Emogrifier / CSS Inliner # The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases. scssphp # scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details. Constant Time Encoder # WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Third Party Libraries"},{"location":"migration/wsc53/libraries/#migrating-from-wsc-53-third-party-libraries","text":"","title":"Migrating from WSC 5.3 - Third Party Libraries"},{"location":"migration/wsc53/libraries/#guzzle","text":"The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7.","title":"Guzzle"},{"location":"migration/wsc53/libraries/#emogrifier-css-inliner","text":"The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases.","title":"Emogrifier / CSS Inliner"},{"location":"migration/wsc53/libraries/#scssphp","text":"scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details.","title":"scssphp"},{"location":"migration/wsc53/libraries/#constant-time-encoder","text":"WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Constant Time Encoder"},{"location":"migration/wsc53/php/","text":"Migrating from WSC 5.3 - PHP # Minimum requirements # The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details. Flood Control # To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class! DatabasePackageInstallationPlugin # DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file. PHP Database API # The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed. Captcha # The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details. Search # The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details. Styles # The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details. Tags # Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details. Avatars # The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"PHP API"},{"location":"migration/wsc53/php/#migrating-from-wsc-53-php","text":"","title":"Migrating from WSC 5.3 - PHP"},{"location":"migration/wsc53/php/#minimum-requirements","text":"The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details.","title":"Minimum requirements"},{"location":"migration/wsc53/php/#flood-control","text":"To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class!","title":"Flood Control"},{"location":"migration/wsc53/php/#databasepackageinstallationplugin","text":"DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file.","title":"DatabasePackageInstallationPlugin"},{"location":"migration/wsc53/php/#php-database-api","text":"The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed.","title":"PHP Database API"},{"location":"migration/wsc53/php/#captcha","text":"The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details.","title":"Captcha"},{"location":"migration/wsc53/php/#search","text":"The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details.","title":"Search"},{"location":"migration/wsc53/php/#styles","text":"The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details.","title":"Styles"},{"location":"migration/wsc53/php/#tags","text":"Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details.","title":"Tags"},{"location":"migration/wsc53/php/#avatars","text":"The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"Avatars"},{"location":"migration/wsc53/session/","text":"Migrating from WSC 5.3 - Session Handling and Authentication # WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data. Summary and Concepts # Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other. Legacy Persistent Login # The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight. Multiple Sessions # To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler . Merged ACP and Frontend Sessions # WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa. Improved Authentication and Reauthentication # WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence. Additions and Changes # Password Hashing # WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems. Session Storage # The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class! Reauthentication # For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 . Multi-factor Authentication # To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in. Adding Multi-factor Methods # Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions. Deprecations and Removals # SessionHandler # Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details. ACP Sessions # The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions. Cookies # The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed. Virtual Sessions # The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables. Security Token Constants # The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions. PasswordUtil and Double BCrypt Hashes # Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"Session Handling and Authentication"},{"location":"migration/wsc53/session/#migrating-from-wsc-53-session-handling-and-authentication","text":"WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data.","title":"Migrating from WSC 5.3 - Session Handling and Authentication"},{"location":"migration/wsc53/session/#summary-and-concepts","text":"Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other.","title":"Summary and Concepts"},{"location":"migration/wsc53/session/#legacy-persistent-login","text":"The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight.","title":"Legacy Persistent Login"},{"location":"migration/wsc53/session/#multiple-sessions","text":"To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler .","title":"Multiple Sessions"},{"location":"migration/wsc53/session/#merged-acp-and-frontend-sessions","text":"WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa.","title":"Merged ACP and Frontend Sessions"},{"location":"migration/wsc53/session/#improved-authentication-and-reauthentication","text":"WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence.","title":"Improved Authentication and Reauthentication"},{"location":"migration/wsc53/session/#additions-and-changes","text":"","title":"Additions and Changes"},{"location":"migration/wsc53/session/#password-hashing","text":"WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems.","title":"Password Hashing"},{"location":"migration/wsc53/session/#session-storage","text":"The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class!","title":"Session Storage"},{"location":"migration/wsc53/session/#reauthentication","text":"For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 .","title":"Reauthentication"},{"location":"migration/wsc53/session/#multi-factor-authentication","text":"To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in.","title":"Multi-factor Authentication"},{"location":"migration/wsc53/session/#adding-multi-factor-methods","text":"Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions.","title":"Adding Multi-factor Methods"},{"location":"migration/wsc53/session/#deprecations-and-removals","text":"","title":"Deprecations and Removals"},{"location":"migration/wsc53/session/#sessionhandler","text":"Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details.","title":"SessionHandler"},{"location":"migration/wsc53/session/#acp-sessions","text":"The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions.","title":"ACP Sessions"},{"location":"migration/wsc53/session/#cookies","text":"The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed.","title":"Cookies"},{"location":"migration/wsc53/session/#virtual-sessions","text":"The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables.","title":"Virtual Sessions"},{"location":"migration/wsc53/session/#security-token-constants","text":"The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions.","title":"Security Token Constants"},{"location":"migration/wsc53/session/#passwordutil-and-double-bcrypt-hashes","text":"Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"PasswordUtil and Double BCrypt Hashes"},{"location":"migration/wsc53/templates/","text":"Migrating from WSC 5.3 - Templates and Languages # {csrfToken} # Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"Templates"},{"location":"migration/wsc53/templates/#migrating-from-wsc-53-templates-and-languages","text":"","title":"Migrating from WSC 5.3 - Templates and Languages"},{"location":"migration/wsc53/templates/#csrftoken","text":"Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"{csrfToken}"},{"location":"package/database-php-api/","text":"Database PHP API # Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ]; Database Tables # There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop () Columns # To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull () Foreign Keys # Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data. Indices # Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Database PHP API"},{"location":"package/database-php-api/#database-php-api","text":"Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ];","title":"Database PHP API"},{"location":"package/database-php-api/#database-tables","text":"There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop ()","title":"Database Tables"},{"location":"package/database-php-api/#columns","text":"To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull ()","title":"Columns"},{"location":"package/database-php-api/#foreign-keys","text":"Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data.","title":"Foreign Keys"},{"location":"package/database-php-api/#indices","text":"Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Indices"},{"location":"package/package-xml/","text":"package.xml # The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package> Elements # <package> # The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ). <packageinformation> # Holds the entire meta data of the package. <packagename> # This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation> <packagedescription> # Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details. <version> # The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction) <date> # Must be a valid ISO 8601 date, e.g. 2013-12-27 . <authorinformation> # Holds meta data regarding the package's author. <author> # Can be anything you want. <authorurl> # (optional) URL to the author's website. <requiredpackages> # A list of packages including their version required for this package to work. <requiredpackage> # Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml . <optionalpackage> # A list of optional packages which can be selected by the user at the very end of the installation process. <optionalpackage> # Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml . <excludedpackages> # List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed. <excludedpackage> # Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher. <compatibility> # Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility> Existing API versions # WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a <instructions> # List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first. <instructions type=\"install\"> # List of instructions for a new installation of this package. <instructions type=\"update\" fromversion=\"\u2026\"> # The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 . <instruction> # Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" /> <void/> # Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"package.xml"},{"location":"package/package-xml/#packagexml","text":"The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version.","title":"package.xml"},{"location":"package/package-xml/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package>","title":"Example"},{"location":"package/package-xml/#elements","text":"","title":"Elements"},{"location":"package/package-xml/#package","text":"The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ).","title":"&lt;package&gt;"},{"location":"package/package-xml/#packageinformation","text":"Holds the entire meta data of the package.","title":"&lt;packageinformation&gt;"},{"location":"package/package-xml/#packagename","text":"This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation>","title":"&lt;packagename&gt;"},{"location":"package/package-xml/#packagedescription","text":"Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details.","title":"&lt;packagedescription&gt;"},{"location":"package/package-xml/#version","text":"The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction)","title":"&lt;version&gt;"},{"location":"package/package-xml/#date","text":"Must be a valid ISO 8601 date, e.g. 2013-12-27 .","title":"&lt;date&gt;"},{"location":"package/package-xml/#authorinformation","text":"Holds meta data regarding the package's author.","title":"&lt;authorinformation&gt;"},{"location":"package/package-xml/#author","text":"Can be anything you want.","title":"&lt;author&gt;"},{"location":"package/package-xml/#authorurl","text":"(optional) URL to the author's website.","title":"&lt;authorurl&gt;"},{"location":"package/package-xml/#requiredpackages","text":"A list of packages including their version required for this package to work.","title":"&lt;requiredpackages&gt;"},{"location":"package/package-xml/#requiredpackage","text":"Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml .","title":"&lt;requiredpackage&gt;"},{"location":"package/package-xml/#optionalpackage","text":"A list of optional packages which can be selected by the user at the very end of the installation process.","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#optionalpackage_1","text":"Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml .","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#excludedpackages","text":"List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed.","title":"&lt;excludedpackages&gt;"},{"location":"package/package-xml/#excludedpackage","text":"Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher.","title":"&lt;excludedpackage&gt;"},{"location":"package/package-xml/#compatibility","text":"Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility>","title":"&lt;compatibility&gt;"},{"location":"package/package-xml/#existing-api-versions","text":"WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a","title":"Existing API versions"},{"location":"package/package-xml/#instructions","text":"List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first.","title":"&lt;instructions&gt;"},{"location":"package/package-xml/#instructions-typeinstall","text":"List of instructions for a new installation of this package.","title":"&lt;instructions type=\"install\"&gt;"},{"location":"package/package-xml/#instructions-typeupdate-fromversion","text":"The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 .","title":"&lt;instructions type=\"update\" fromversion=\"\u2026\"&gt;"},{"location":"package/package-xml/#instruction","text":"Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" />","title":"&lt;instruction&gt;"},{"location":"package/package-xml/#void","text":"Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"&lt;void/&gt;"},{"location":"package/pip/","text":"Package Installation Plugins # Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary. Built-In PIPs # Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Overview"},{"location":"package/pip/#package-installation-plugins","text":"Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary.","title":"Package Installation Plugins"},{"location":"package/pip/#built-in-pips","text":"Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Built-In PIPs"},{"location":"package/pip/acl-option/","text":"ACL Option Package Installation Plugin # Add customizable permissions for individual objects. Option Components # Each acl option is described as an <option> element with the mandatory attribute name . <categoryname> # Optional The name of the acl option category to which the option belongs. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Category Components # Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"aclOption"},{"location":"package/pip/acl-option/#acl-option-package-installation-plugin","text":"Add customizable permissions for individual objects.","title":"ACL Option Package Installation Plugin"},{"location":"package/pip/acl-option/#option-components","text":"Each acl option is described as an <option> element with the mandatory attribute name .","title":"Option Components"},{"location":"package/pip/acl-option/#categoryname","text":"Optional The name of the acl option category to which the option belongs.","title":"&lt;categoryname&gt;"},{"location":"package/pip/acl-option/#objecttype","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#category-components","text":"Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value.","title":"Category Components"},{"location":"package/pip/acl-option/#objecttype_1","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"Example"},{"location":"package/pip/acp-menu/","text":"ACP Menu Package Installation Plugin # Registers new ACP menu items. Components # Each item is described as an <acpmenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <icon> # Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"acpMenu"},{"location":"package/pip/acp-menu/#acp-menu-package-installation-plugin","text":"Registers new ACP menu items.","title":"ACP Menu Package Installation Plugin"},{"location":"package/pip/acp-menu/#components","text":"Each item is described as an <acpmenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/acp-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/acp-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/acp-menu/#icon","text":"Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class.","title":"&lt;icon&gt;"},{"location":"package/pip/acp-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/acp-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/acp-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"Example"},{"location":"package/pip/acp-search-provider/","text":"ACP Search Provider Package Installation Plugin # Registers data provider for the admin panel search. Components # Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name . <classname> # The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface. <showorder> # Optional Determines at which position of the search result list the provided results are shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"acpSearchProvider"},{"location":"package/pip/acp-search-provider/#acp-search-provider-package-installation-plugin","text":"Registers data provider for the admin panel search.","title":"ACP Search Provider Package Installation Plugin"},{"location":"package/pip/acp-search-provider/#components","text":"Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-search-provider/#classname","text":"The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/acp-search-provider/#showorder","text":"Optional Determines at which position of the search result list the provided results are shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-search-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"Example"},{"location":"package/pip/acp-template/","text":"ACP Template Installation Plugin # Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages. Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file. Attributes # application # The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory. Example in package.xml # <instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"acpTemplate"},{"location":"package/pip/acp-template/#acp-template-installation-plugin","text":"Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages.","title":"ACP Template Installation Plugin"},{"location":"package/pip/acp-template/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/acp-template/#attributes","text":"","title":"Attributes"},{"location":"package/pip/acp-template/#application","text":"The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory.","title":"application"},{"location":"package/pip/acp-template/#example-in-packagexml","text":"<instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"Example in package.xml"},{"location":"package/pip/bbcode/","text":"BBCode Package Installation Plugin # Registers new BBCodes. Components # Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user. <htmlopen> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode. <htmlclose> # Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags. <classname> # The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered. <wysiwygicon> # Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar. <buttonlabel> # Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon. <sourcecode> # Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead. <isBlockElement> # Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification). <attributes> # Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer. <html> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value. <validationpattern> # Optional Defines a regular expression that is used to validate the value of the attribute. <required> # Optional Specifies whether this attribute must be provided. <usetext> # Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"bbcode"},{"location":"package/pip/bbcode/#bbcode-package-installation-plugin","text":"Registers new BBCodes.","title":"BBCode Package Installation Plugin"},{"location":"package/pip/bbcode/#components","text":"Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user.","title":"Components"},{"location":"package/pip/bbcode/#htmlopen","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode.","title":"&lt;htmlopen&gt;"},{"location":"package/pip/bbcode/#htmlclose","text":"Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags.","title":"&lt;htmlclose&gt;"},{"location":"package/pip/bbcode/#classname","text":"The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered.","title":"&lt;classname&gt;"},{"location":"package/pip/bbcode/#wysiwygicon","text":"Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar.","title":"&lt;wysiwygicon&gt;"},{"location":"package/pip/bbcode/#buttonlabel","text":"Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon.","title":"&lt;buttonlabel&gt;"},{"location":"package/pip/bbcode/#sourcecode","text":"Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead.","title":"&lt;sourcecode&gt;"},{"location":"package/pip/bbcode/#isblockelement","text":"Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification).","title":"&lt;isBlockElement&gt;"},{"location":"package/pip/bbcode/#attributes","text":"Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer.","title":"&lt;attributes&gt;"},{"location":"package/pip/bbcode/#html","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value.","title":"&lt;html&gt;"},{"location":"package/pip/bbcode/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of the attribute.","title":"&lt;validationpattern&gt;"},{"location":"package/pip/bbcode/#required","text":"Optional Specifies whether this attribute must be provided.","title":"&lt;required&gt;"},{"location":"package/pip/bbcode/#usetext","text":"Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value.","title":"&lt;usetext&gt;"},{"location":"package/pip/bbcode/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"Example"},{"location":"package/pip/box/","text":"Box Package Installation Plugin # Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based. Components # Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity . <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <boxType> # system # The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <objectType> # Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController . <position> # The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top Placeholder Positions # --8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\" <showHeader> # Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 . <visibleEverywhere> # Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> . <visibilityExceptions> # Inverts the <visibleEverywhere> setting for the listed pages only. <cssClassName> # Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the box title shown to the end users. <content> # The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"box"},{"location":"package/pip/box/#box-package-installation-plugin","text":"Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based.","title":"Box Package Installation Plugin"},{"location":"package/pip/box/#components","text":"Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity .","title":"Components"},{"location":"package/pip/box/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/box/#boxtype","text":"","title":"&lt;boxType&gt;"},{"location":"package/pip/box/#system","text":"The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element.","title":"system"},{"location":"package/pip/box/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/box/#objecttype","text":"Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController .","title":"&lt;objectType&gt;"},{"location":"package/pip/box/#position","text":"The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top","title":"&lt;position&gt;"},{"location":"package/pip/box/#placeholder-positions","text":"--8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\"","title":"Placeholder Positions"},{"location":"package/pip/box/#showheader","text":"Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 .","title":"&lt;showHeader&gt;"},{"location":"package/pip/box/#visibleeverywhere","text":"Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> .","title":"&lt;visibleEverywhere&gt;"},{"location":"package/pip/box/#visibilityexceptions","text":"Inverts the <visibleEverywhere> setting for the listed pages only.","title":"&lt;visibilityExceptions&gt;"},{"location":"package/pip/box/#cssclassname","text":"Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance.","title":"&lt;cssClassName&gt;"},{"location":"package/pip/box/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/box/#title","text":"The title element is required and controls the box title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/box/#content_1","text":"The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/box/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"Example"},{"location":"package/pip/clipboard-action/","text":"Clipboard Action Package Installation Plugin # Registers clipboard actions. Components # Each clipboard action is described as an <action> element with the mandatory attribute name . <actionclassname> # The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction . <pages> # Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available. <showorder> # Optional Determines at which position of the clipboard action list the action is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"clipboardAction"},{"location":"package/pip/clipboard-action/#clipboard-action-package-installation-plugin","text":"Registers clipboard actions.","title":"Clipboard Action Package Installation Plugin"},{"location":"package/pip/clipboard-action/#components","text":"Each clipboard action is described as an <action> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/clipboard-action/#actionclassname","text":"The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction .","title":"&lt;actionclassname&gt;"},{"location":"package/pip/clipboard-action/#pages","text":"Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available.","title":"&lt;pages&gt;"},{"location":"package/pip/clipboard-action/#showorder","text":"Optional Determines at which position of the clipboard action list the action is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/clipboard-action/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"Example"},{"location":"package/pip/core-object/","text":"Core Object Package Installation Plugin # Registers wcf\\system\\SingletonFactory objects to be accessible in templates. Components # Each item is described as a <coreobject> element with the mandatory element objectname . <objectname> # The fully qualified class name of the class. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"coreObject"},{"location":"package/pip/core-object/#core-object-package-installation-plugin","text":"Registers wcf\\system\\SingletonFactory objects to be accessible in templates.","title":"Core Object Package Installation Plugin"},{"location":"package/pip/core-object/#components","text":"Each item is described as a <coreobject> element with the mandatory element objectname .","title":"Components"},{"location":"package/pip/core-object/#objectname","text":"The fully qualified class name of the class.","title":"&lt;objectname&gt;"},{"location":"package/pip/core-object/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"Example"},{"location":"package/pip/cronjob/","text":"Cronjob Package Installation Plugin # Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past. Components # Each cronjob is described as an <cronjob> element with the mandatory attribute name . <classname> # The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface. <description> # The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator. <start*> # All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax. <canBeEdited> # Controls whether the administrator may edit the fields of the cronjob. <canBeDisabled> # Controls whether the administrator may disable the cronjob. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"cronjob"},{"location":"package/pip/cronjob/#cronjob-package-installation-plugin","text":"Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past.","title":"Cronjob Package Installation Plugin"},{"location":"package/pip/cronjob/#components","text":"Each cronjob is described as an <cronjob> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/cronjob/#classname","text":"The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface.","title":"&lt;classname&gt;"},{"location":"package/pip/cronjob/#description","text":"The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator.","title":"&lt;description&gt;"},{"location":"package/pip/cronjob/#start","text":"All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax.","title":"&lt;start*&gt;"},{"location":"package/pip/cronjob/#canbeedited","text":"Controls whether the administrator may edit the fields of the cronjob.","title":"&lt;canBeEdited&gt;"},{"location":"package/pip/cronjob/#canbedisabled","text":"Controls whether the administrator may disable the cronjob.","title":"&lt;canBeDisabled&gt;"},{"location":"package/pip/cronjob/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/cronjob/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"Example"},{"location":"package/pip/database/","text":"Database Package Installation Plugin # Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename. Execution environment # The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#database-package-installation-plugin","text":"Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#attributes","text":"","title":"Attributes"},{"location":"package/pip/database/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/database/#expected-value","text":"The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects.","title":"Expected value"},{"location":"package/pip/database/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename.","title":"Naming convention"},{"location":"package/pip/database/#execution-environment","text":"The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Execution environment"},{"location":"package/pip/event-listener/","text":"Event Listener Package Installation Plugin # Registers event listeners. An explanation of events and event listeners can be found here . Components # Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <eventclassname> # The event class name is the name of the class in which the event is fired. <eventname> # The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events. <listenerclassname> # The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface! <inherit> # The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value. <environment> # The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ). <nice> # The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed. <permissions> # The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"eventListener"},{"location":"package/pip/event-listener/#event-listener-package-installation-plugin","text":"Registers event listeners. An explanation of events and event listeners can be found here .","title":"Event Listener Package Installation Plugin"},{"location":"package/pip/event-listener/#components","text":"Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/event-listener/#eventclassname","text":"The event class name is the name of the class in which the event is fired.","title":"&lt;eventclassname&gt;"},{"location":"package/pip/event-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events.","title":"&lt;eventname&gt;"},{"location":"package/pip/event-listener/#listenerclassname","text":"The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface!","title":"&lt;listenerclassname&gt;"},{"location":"package/pip/event-listener/#inherit","text":"The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value.","title":"&lt;inherit&gt;"},{"location":"package/pip/event-listener/#environment","text":"The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ).","title":"&lt;environment&gt;"},{"location":"package/pip/event-listener/#nice","text":"The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/event-listener/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/event-listener/#permissions","text":"The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/event-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/file/","text":"File Package Installation Plugin # Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates . Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file. Example in package.xml # <instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"file"},{"location":"package/pip/file/#file-package-installation-plugin","text":"Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates .","title":"File Package Installation Plugin"},{"location":"package/pip/file/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/file/#example-in-packagexml","text":"<instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"Example in package.xml"},{"location":"package/pip/language/","text":"Language Package Installation Plugin # Registers new language items. Components # The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute. <category> # Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). <item> # Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"language"},{"location":"package/pip/language/#language-package-installation-plugin","text":"Registers new language items.","title":"Language Package Installation Plugin"},{"location":"package/pip/language/#components","text":"The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute.","title":"Components"},{"location":"package/pip/language/#category","text":"Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E).","title":"&lt;category&gt;"},{"location":"package/pip/language/#item","text":"Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting.","title":"&lt;item&gt;"},{"location":"package/pip/language/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"Example"},{"location":"package/pip/media-provider/","text":"Media Provider Package Installation Plugin # Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages. Components # Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist . <title> # The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users. <regex> # The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group. <className> # <className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider . <html> # <className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"mediaProvider"},{"location":"package/pip/media-provider/#media-provider-package-installation-plugin","text":"Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages.","title":"Media Provider Package Installation Plugin"},{"location":"package/pip/media-provider/#components","text":"Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist .","title":"Components"},{"location":"package/pip/media-provider/#title","text":"The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users.","title":"&lt;title&gt;"},{"location":"package/pip/media-provider/#regex","text":"The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group.","title":"&lt;regex&gt;"},{"location":"package/pip/media-provider/#classname","text":"<className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider .","title":"&lt;className&gt;"},{"location":"package/pip/media-provider/#html","text":"<className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} .","title":"&lt;html&gt;"},{"location":"package/pip/media-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu-item/","text":"Menu Item Package Installation Plugin # Adds menu items to existing menus. Components # Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard . <menu> # The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml . <title> # The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values. <page> # The page that the link should point to, requires the internal identifier set by creating a page through the page.xml . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menuItem"},{"location":"package/pip/menu-item/#menu-item-package-installation-plugin","text":"Adds menu items to existing menus.","title":"Menu Item Package Installation Plugin"},{"location":"package/pip/menu-item/#components","text":"Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard .","title":"Components"},{"location":"package/pip/menu-item/#menu","text":"The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml .","title":"&lt;menu&gt;"},{"location":"package/pip/menu-item/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values.","title":"&lt;title&gt;"},{"location":"package/pip/menu-item/#page","text":"The page that the link should point to, requires the internal identifier set by creating a page through the page.xml .","title":"&lt;page&gt;"},{"location":"package/pip/menu-item/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu/","text":"Menu Package Installation Plugin # Deploy and manage menus that can be placed anywhere on the site. Components # Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu . <title> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements. <box> # The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menu"},{"location":"package/pip/menu/#menu-package-installation-plugin","text":"Deploy and manage menus that can be placed anywhere on the site.","title":"Menu Package Installation Plugin"},{"location":"package/pip/menu/#components","text":"Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu .","title":"Components"},{"location":"package/pip/menu/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements.","title":"&lt;title&gt;"},{"location":"package/pip/menu/#box","text":"The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName","title":"&lt;box&gt;"},{"location":"package/pip/menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/object-type-definition/","text":"Object Type Definition Package Installation Plugin # Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system. Components # Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <interfacename> # Optional The name of the PHP interface objectTypes have to implement. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"objectTypeDefinition"},{"location":"package/pip/object-type-definition/#object-type-definition-package-installation-plugin","text":"Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system.","title":"Object Type Definition Package Installation Plugin"},{"location":"package/pip/object-type-definition/#components","text":"Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type-definition/#interfacename","text":"Optional The name of the PHP interface objectTypes have to implement.","title":"&lt;interfacename&gt;"},{"location":"package/pip/object-type-definition/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"Example"},{"location":"package/pip/object-type/","text":"Object Type Package Installation Plugin # Registers an object type. Read about object types in the objectTypeDefinition PIP. Components # Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <definitionname> # The <name> of the objectTypeDefinition . <classname> # The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition. <*> # Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"objectType"},{"location":"package/pip/object-type/#object-type-package-installation-plugin","text":"Registers an object type. Read about object types in the objectTypeDefinition PIP.","title":"Object Type Package Installation Plugin"},{"location":"package/pip/object-type/#components","text":"Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type/#definitionname","text":"The <name> of the objectTypeDefinition .","title":"&lt;definitionname&gt;"},{"location":"package/pip/object-type/#classname","text":"The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition.","title":"&lt;classname&gt;"},{"location":"package/pip/object-type/#_1","text":"Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/object-type/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"Example"},{"location":"package/pip/option/","text":"Option Package Installation Plugin # Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants. Category Components # Each category is described as an <category> element with the mandatory attribute name . <parent> # Optional The category\u2019s parent category. <showorder> # Optional Specifies the order of this option within the parent category. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator. Option Components # Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it. <categoryname> # The option\u2019s category. <optiontype> # The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes. <defaultvalue> # The value that is set after installation of a package. Valid values are defined by the optiontype . <validationpattern> # Optional Defines a regular expression that is used to validate the value of a free form option (such as text ). <showorder> # Optional Specifies the order of this option within the category. <selectoptions> # Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code. <enableoptions> # Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option. <hidden> # Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator. <supporti18n> # Optional Specifies whether this option supports localized input. <requirei18n> # Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language). <*> # Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation. Language Items # All relevant language items have to be put into the wcf.acp.option language item category. Categories # If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"option"},{"location":"package/pip/option/#option-package-installation-plugin","text":"Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants.","title":"Option Package Installation Plugin"},{"location":"package/pip/option/#category-components","text":"Each category is described as an <category> element with the mandatory attribute name .","title":"Category Components"},{"location":"package/pip/option/#parent","text":"Optional The category\u2019s parent category.","title":"&lt;parent&gt;"},{"location":"package/pip/option/#showorder","text":"Optional Specifies the order of this option within the parent category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#option-components","text":"Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it.","title":"Option Components"},{"location":"package/pip/option/#categoryname","text":"The option\u2019s category.","title":"&lt;categoryname&gt;"},{"location":"package/pip/option/#optiontype","text":"The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes.","title":"&lt;optiontype&gt;"},{"location":"package/pip/option/#defaultvalue","text":"The value that is set after installation of a package. Valid values are defined by the optiontype .","title":"&lt;defaultvalue&gt;"},{"location":"package/pip/option/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of a free form option (such as text ).","title":"&lt;validationpattern&gt;"},{"location":"package/pip/option/#showorder_1","text":"Optional Specifies the order of this option within the category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#selectoptions","text":"Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code.","title":"&lt;selectoptions&gt;"},{"location":"package/pip/option/#enableoptions","text":"Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option.","title":"&lt;enableoptions&gt;"},{"location":"package/pip/option/#hidden","text":"Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically.","title":"&lt;hidden&gt;"},{"location":"package/pip/option/#options_1","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#supporti18n","text":"Optional Specifies whether this option supports localized input.","title":"&lt;supporti18n&gt;"},{"location":"package/pip/option/#requirei18n","text":"Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language).","title":"&lt;requirei18n&gt;"},{"location":"package/pip/option/#_1","text":"Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/option/#language-items","text":"All relevant language items have to be put into the wcf.acp.option language item category.","title":"Language Items"},{"location":"package/pip/option/#categories","text":"If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/option/#options_2","text":"If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description .","title":"Options"},{"location":"package/pip/option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/page/","text":"Page Package Installation Plugin # Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus. Components # Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList . <pageType> # system # The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <controller> # Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm . <handler> # Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id. <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <parent> # Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy. <hasFixedParent> # Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable. <permissions> # The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set. <options> # The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set. <excludeFromLandingPage> # Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the page title shown to the end users. <content> # The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"page"},{"location":"package/pip/page/#page-package-installation-plugin","text":"Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus.","title":"Page Package Installation Plugin"},{"location":"package/pip/page/#components","text":"Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList .","title":"Components"},{"location":"package/pip/page/#pagetype","text":"","title":"&lt;pageType&gt;"},{"location":"package/pip/page/#system","text":"The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element.","title":"system"},{"location":"package/pip/page/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/page/#controller","text":"Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm .","title":"&lt;controller&gt;"},{"location":"package/pip/page/#handler","text":"Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id.","title":"&lt;handler&gt;"},{"location":"package/pip/page/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/page/#parent","text":"Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy.","title":"&lt;parent&gt;"},{"location":"package/pip/page/#hasfixedparent","text":"Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable.","title":"&lt;hasFixedParent&gt;"},{"location":"package/pip/page/#permissions","text":"The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set.","title":"&lt;permissions&gt;"},{"location":"package/pip/page/#options","text":"The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set.","title":"&lt;options&gt;"},{"location":"package/pip/page/#excludefromlandingpage","text":"Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever.","title":"&lt;excludeFromLandingPage&gt;"},{"location":"package/pip/page/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/page/#title","text":"The title element is required and controls the page title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/page/#content_1","text":"The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/page/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"Example"},{"location":"package/pip/pip/","text":"Package Installation Plugin Package Installation Plugin # Registers new package installation plugins. Components # Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation! Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"pip"},{"location":"package/pip/pip/#package-installation-plugin-package-installation-plugin","text":"Registers new package installation plugins.","title":"Package Installation Plugin Package Installation Plugin"},{"location":"package/pip/pip/#components","text":"Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation!","title":"Components"},{"location":"package/pip/pip/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"Example"},{"location":"package/pip/script/","text":"Script Package Installation Plugin # Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The script -PIP expects a relative path to a .php file. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . Execution environment # The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"script"},{"location":"package/pip/script/#script-package-installation-plugin","text":"Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Script Package Installation Plugin"},{"location":"package/pip/script/#attributes","text":"","title":"Attributes"},{"location":"package/pip/script/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/script/#expected-value","text":"The script -PIP expects a relative path to a .php file.","title":"Expected value"},{"location":"package/pip/script/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 .","title":"Naming convention"},{"location":"package/pip/script/#execution-environment","text":"The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"Execution environment"},{"location":"package/pip/smiley/","text":"Smiley Package Installation Plugin # Installs new smileys. Components # Each smiley is described as an <smiley> element with the mandatory attribute name . <title> # Short human readable description of the smiley. <path(2x)?> # The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens. <aliases> # Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A). <showorder> # Optional Determines at which position of the smiley list the smiley is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"smiley"},{"location":"package/pip/smiley/#smiley-package-installation-plugin","text":"Installs new smileys.","title":"Smiley Package Installation Plugin"},{"location":"package/pip/smiley/#components","text":"Each smiley is described as an <smiley> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/smiley/#title","text":"Short human readable description of the smiley.","title":"&lt;title&gt;"},{"location":"package/pip/smiley/#path2x","text":"The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens.","title":"&lt;path(2x)?&gt;"},{"location":"package/pip/smiley/#aliases","text":"Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A).","title":"&lt;aliases&gt;"},{"location":"package/pip/smiley/#showorder","text":"Optional Determines at which position of the smiley list the smiley is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/smiley/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"Example"},{"location":"package/pip/sql/","text":"SQL Package Installation Plugin # Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard. Expected Value # The sql package installation plugin expects a relative path to a .sql file. Features # Logging # WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY Instance Number # It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 ! Table Type # WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used. Limitations # Logging # WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE . Triggers # WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script . Example # package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"sql"},{"location":"package/pip/sql/#sql-package-installation-plugin","text":"Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard.","title":"SQL Package Installation Plugin"},{"location":"package/pip/sql/#expected-value","text":"The sql package installation plugin expects a relative path to a .sql file.","title":"Expected Value"},{"location":"package/pip/sql/#features","text":"","title":"Features"},{"location":"package/pip/sql/#logging","text":"WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY","title":"Logging"},{"location":"package/pip/sql/#instance-number","text":"It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 !","title":"Instance Number"},{"location":"package/pip/sql/#table-type","text":"WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used.","title":"Table Type"},{"location":"package/pip/sql/#limitations","text":"","title":"Limitations"},{"location":"package/pip/sql/#logging_1","text":"WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE .","title":"Logging"},{"location":"package/pip/sql/#triggers","text":"WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script .","title":"Triggers"},{"location":"package/pip/sql/#example","text":"package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"Example"},{"location":"package/pip/style/","text":"Style Package Installation Plugin # Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles. Example in package.xml # <instruction type= \"style\" > style.tgz </instruction>","title":"style"},{"location":"package/pip/style/#style-package-installation-plugin","text":"Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles.","title":"Style Package Installation Plugin"},{"location":"package/pip/style/#example-in-packagexml","text":"<instruction type= \"style\" > style.tgz </instruction>","title":"Example in package.xml"},{"location":"package/pip/template-listener/","text":"Template Listener Package Installation Plugin # Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables). Components # Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <templatename> # The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners. <eventname> # The event name is the name given when the event is fired to identify different events within the same template. <templatecode> # The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention. <environment> # The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ). <nice> # Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"templateListener"},{"location":"package/pip/template-listener/#template-listener-package-installation-plugin","text":"Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables).","title":"Template Listener Package Installation Plugin"},{"location":"package/pip/template-listener/#components","text":"Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/template-listener/#templatename","text":"The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners.","title":"&lt;templatename&gt;"},{"location":"package/pip/template-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same template.","title":"&lt;eventname&gt;"},{"location":"package/pip/template-listener/#templatecode","text":"The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention.","title":"&lt;templatecode&gt;"},{"location":"package/pip/template-listener/#environment","text":"The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ).","title":"&lt;environment&gt;"},{"location":"package/pip/template-listener/#nice","text":"Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/template-listener/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/template-listener/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/template-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/template/","text":"Template Package Installation Plugin # Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"template"},{"location":"package/pip/template/#template-package-installation-plugin","text":"Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"Template Package Installation Plugin"},{"location":"package/pip/user-group-option/","text":"User Group Option Package Installation Plugin # Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <(admin|mod|user)defaultvalue> # Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS . <usersonly> # Makes the option unavailable for groups with the group type UserGroup::GUESTS . Language Items # All relevant language items have to be put into the wcf.acp.group language item category. Categories # If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"userGroupOption"},{"location":"package/pip/user-group-option/#user-group-option-package-installation-plugin","text":"Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Group Option Package Installation Plugin"},{"location":"package/pip/user-group-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-group-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-group-option/#adminmoduserdefaultvalue","text":"Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS .","title":"&lt;(admin|mod|user)defaultvalue&gt;"},{"location":"package/pip/user-group-option/#usersonly","text":"Makes the option unavailable for groups with the group type UserGroup::GUESTS .","title":"&lt;usersonly&gt;"},{"location":"package/pip/user-group-option/#language-items","text":"All relevant language items have to be put into the wcf.acp.group language item category.","title":"Language Items"},{"location":"package/pip/user-group-option/#categories","text":"If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-group-option/#options","text":"If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"Options"},{"location":"package/pip/user-menu/","text":"User Menu Package Installation Plugin # Registers new user menu items. Components # Each item is described as an <usermenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <iconclassname> # Use an icon only for top-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown. <classname> # The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"userMenu"},{"location":"package/pip/user-menu/#user-menu-package-installation-plugin","text":"Registers new user menu items.","title":"User Menu Package Installation Plugin"},{"location":"package/pip/user-menu/#components","text":"Each item is described as an <usermenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/user-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/user-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/user-menu/#iconclassname","text":"Use an icon only for top-level items. Name of the Font Awesome icon class.","title":"&lt;iconclassname&gt;"},{"location":"package/pip/user-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-menu/#classname","text":"The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"Example"},{"location":"package/pip/user-notification-event/","text":"User Notification Event Package Installation Plugin # Registers new user notification events. Components # Each package installation plugin is described as an <event> element with the mandatory child <name> . <objectType> # The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition. <classname> # The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface. <preset> # Defines whether this event is enabled by default. <presetmailnotificationtype> # Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"userNotificationEvent"},{"location":"package/pip/user-notification-event/#user-notification-event-package-installation-plugin","text":"Registers new user notification events.","title":"User Notification Event Package Installation Plugin"},{"location":"package/pip/user-notification-event/#components","text":"Each package installation plugin is described as an <event> element with the mandatory child <name> .","title":"Components"},{"location":"package/pip/user-notification-event/#objecttype","text":"The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition.","title":"&lt;objectType&gt;"},{"location":"package/pip/user-notification-event/#classname","text":"The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-notification-event/#preset","text":"Defines whether this event is enabled by default.","title":"&lt;preset&gt;"},{"location":"package/pip/user-notification-event/#presetmailnotificationtype","text":"Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default.","title":"&lt;presetmailnotificationtype&gt;"},{"location":"package/pip/user-notification-event/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available.","title":"&lt;options&gt;"},{"location":"package/pip/user-notification-event/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-notification-event/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"Example"},{"location":"package/pip/user-option/","text":"User Option Package Installation Plugin # Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <required> # Requires that a value is provided. <askduringregistration> # If set to 1 the field is shown during user registration in the frontend. <editable> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4 <visible> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8 <searchable> # If set to 1 the field is searchable. <outputclass> # PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface. Language Items # All relevant language items have to be put into the wcf.user.option language item category. Categories # If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"userOption"},{"location":"package/pip/user-option/#user-option-package-installation-plugin","text":"Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Option Package Installation Plugin"},{"location":"package/pip/user-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-option/#required","text":"Requires that a value is provided.","title":"&lt;required&gt;"},{"location":"package/pip/user-option/#askduringregistration","text":"If set to 1 the field is shown during user registration in the frontend.","title":"&lt;askduringregistration&gt;"},{"location":"package/pip/user-option/#editable","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4","title":"&lt;editable&gt;"},{"location":"package/pip/user-option/#visible","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8","title":"&lt;visible&gt;"},{"location":"package/pip/user-option/#searchable","text":"If set to 1 the field is searchable.","title":"&lt;searchable&gt;"},{"location":"package/pip/user-option/#outputclass","text":"PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface.","title":"&lt;outputclass&gt;"},{"location":"package/pip/user-option/#language-items","text":"All relevant language items have to be put into the wcf.user.option language item category.","title":"Language Items"},{"location":"package/pip/user-option/#categories","text":"If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-option/#options","text":"If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"Options"},{"location":"package/pip/user-profile-menu/","text":"User Profile Menu Package Installation Plugin # Registers new user profile tabs. Components # Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name . <classname> # The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface. <showorder> # Optional Determines at which position of the tab list the tab is shown. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"userProfileMenu"},{"location":"package/pip/user-profile-menu/#user-profile-menu-package-installation-plugin","text":"Registers new user profile tabs.","title":"User Profile Menu Package Installation Plugin"},{"location":"package/pip/user-profile-menu/#components","text":"Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-profile-menu/#classname","text":"The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-profile-menu/#showorder","text":"Optional Determines at which position of the tab list the tab is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-profile-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-profile-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-profile-menu/#example","text":"<?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"Example"},{"location":"php/apps/","text":"Apps for WoltLab Suite # Introduction # Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No. Differences to Plugins # Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link. Unique Namespace # Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" /> Unique Database Table Prefix # All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number. Separate Domain and Path Configuration # Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself. Creating an App # This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa. package.xml # The package.xml supports two additional elements in the <packageinformation> block that are unique to applications. <isapplication>1</isapplication> # This element is responsible to flag a package as an app. <applicationdirectory>example</applicationdirectory> # Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead. Minimum Required Files # An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Apps"},{"location":"php/apps/#apps-for-woltlab-suite","text":"","title":"Apps for WoltLab Suite"},{"location":"php/apps/#introduction","text":"Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No.","title":"Introduction"},{"location":"php/apps/#differences-to-plugins","text":"Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link.","title":"Differences to Plugins"},{"location":"php/apps/#unique-namespace","text":"Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" />","title":"Unique Namespace"},{"location":"php/apps/#unique-database-table-prefix","text":"All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number.","title":"Unique Database Table Prefix"},{"location":"php/apps/#separate-domain-and-path-configuration","text":"Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself.","title":"Separate Domain and Path Configuration"},{"location":"php/apps/#creating-an-app","text":"This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa.","title":"Creating an App"},{"location":"php/apps/#packagexml","text":"The package.xml supports two additional elements in the <packageinformation> block that are unique to applications.","title":"package.xml"},{"location":"php/apps/#isapplication1isapplication","text":"This element is responsible to flag a package as an app.","title":"&lt;isapplication&gt;1&lt;/isapplication&gt;"},{"location":"php/apps/#applicationdirectoryexampleapplicationdirectory","text":"Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead.","title":"&lt;applicationdirectory&gt;example&lt;/applicationdirectory&gt;"},{"location":"php/apps/#minimum-required-files","text":"An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Minimum Required Files"},{"location":"php/code-style-documentation/","text":"Documentation # The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. Database Objects # Database Table Columns as Properties # As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type Database Object Editors # Class Tags # Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself. Runtime Caches # Class Tags # Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Documentation"},{"location":"php/code-style-documentation/#documentation","text":"The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so.","title":"Documentation"},{"location":"php/code-style-documentation/#database-objects","text":"","title":"Database Objects"},{"location":"php/code-style-documentation/#database-table-columns-as-properties","text":"As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type","title":"Database Table Columns as Properties"},{"location":"php/code-style-documentation/#database-object-editors","text":"","title":"Database Object Editors"},{"location":"php/code-style-documentation/#class-tags","text":"Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself.","title":"Class Tags"},{"location":"php/code-style-documentation/#runtime-caches","text":"","title":"Runtime Caches"},{"location":"php/code-style-documentation/#class-tags_1","text":"Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Class Tags"},{"location":"php/code-style/","text":"Code Style # The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page . General Code Style # Naming conventions # The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING Arrays # For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ]; Ternary Operator # The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; Whitespaces # You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1) Classes # Referencing Class Names # If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ; Static Getters (of DatabaseObject Classes) # Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} . Long method calls # In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Code Style"},{"location":"php/code-style/#code-style","text":"The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page .","title":"Code Style"},{"location":"php/code-style/#general-code-style","text":"","title":"General Code Style"},{"location":"php/code-style/#naming-conventions","text":"The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING","title":"Naming conventions"},{"location":"php/code-style/#arrays","text":"For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ];","title":"Arrays"},{"location":"php/code-style/#ternary-operator","text":"The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ;","title":"Ternary Operator"},{"location":"php/code-style/#whitespaces","text":"You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1)","title":"Whitespaces"},{"location":"php/code-style/#classes","text":"","title":"Classes"},{"location":"php/code-style/#referencing-class-names","text":"If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ;","title":"Referencing Class Names"},{"location":"php/code-style/#static-getters-of-databaseobject-classes","text":"Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} .","title":"Static Getters (of DatabaseObject Classes)"},{"location":"php/code-style/#long-method-calls","text":"In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Long method calls"},{"location":"php/database-access/","text":"Database Access # Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works. The PreparedStatement Object # The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result } Query Parameters # The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result } Fetching a Single Result # Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor. Fetch by Column # There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn (); Fetching All Results # If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value. Building Complex Conditions # Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty! INSERT or UPDATE in Bulk # Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"Database Access"},{"location":"php/database-access/#database-access","text":"Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works.","title":"Database Access"},{"location":"php/database-access/#the-preparedstatement-object","text":"The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"The PreparedStatement Object"},{"location":"php/database-access/#query-parameters","text":"The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"Query Parameters"},{"location":"php/database-access/#fetching-a-single-result","text":"Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor.","title":"Fetching a Single Result"},{"location":"php/database-access/#fetch-by-column","text":"There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn ();","title":"Fetch by Column"},{"location":"php/database-access/#fetching-all-results","text":"If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value.","title":"Fetching All Results"},{"location":"php/database-access/#building-complex-conditions","text":"Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty!","title":"Building Complex Conditions"},{"location":"php/database-access/#insert-or-update-in-bulk","text":"Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"INSERT or UPDATE in Bulk"},{"location":"php/database-objects/","text":"Database Objects # WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text. DatabaseObject # The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually. DatabaseObjectDecorator # If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() . DatabaseObjectEditor # This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model. Create a new row # Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ; Updating an existing row # The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ; Deleting a row # Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete (); DatabaseObjectList # Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; } AbstractDatabaseObjectAction # Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; } Executing an Action # The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction Custom Method with AJAX Support # This section is about adding the method baz() to ExampleAction and calling it via AJAX. AJAX Validation # Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever! create, update and delete # The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } } Allow Invokation by Guests # Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods. ACP Access Only # Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"Database Objects"},{"location":"php/database-objects/#database-objects","text":"WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text.","title":"Database Objects"},{"location":"php/database-objects/#databaseobject","text":"The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually.","title":"DatabaseObject"},{"location":"php/database-objects/#databaseobjectdecorator","text":"If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() .","title":"DatabaseObjectDecorator"},{"location":"php/database-objects/#databaseobjecteditor","text":"This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model.","title":"DatabaseObjectEditor"},{"location":"php/database-objects/#create-a-new-row","text":"Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ;","title":"Create a new row"},{"location":"php/database-objects/#updating-an-existing-row","text":"The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ;","title":"Updating an existing row"},{"location":"php/database-objects/#deleting-a-row","text":"Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete ();","title":"Deleting a row"},{"location":"php/database-objects/#databaseobjectlist","text":"Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; }","title":"DatabaseObjectList"},{"location":"php/database-objects/#abstractdatabaseobjectaction","text":"Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; }","title":"AbstractDatabaseObjectAction"},{"location":"php/database-objects/#executing-an-action","text":"The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction","title":"Executing an Action"},{"location":"php/database-objects/#custom-method-with-ajax-support","text":"This section is about adding the method baz() to ExampleAction and calling it via AJAX.","title":"Custom Method with AJAX Support"},{"location":"php/database-objects/#ajax-validation","text":"Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever!","title":"AJAX Validation"},{"location":"php/database-objects/#create-update-and-delete","text":"The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } }","title":"create, update and delete"},{"location":"php/database-objects/#allow-invokation-by-guests","text":"Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods.","title":"Allow Invokation by Guests"},{"location":"php/database-objects/#acp-access-only","text":"Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"ACP Access Only"},{"location":"php/exceptions/","text":"Exceptions # SPL Exceptions # The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible. Custom Exceptions # Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Exceptions"},{"location":"php/exceptions/#exceptions","text":"","title":"Exceptions"},{"location":"php/exceptions/#spl-exceptions","text":"The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible.","title":"SPL Exceptions"},{"location":"php/exceptions/#custom-exceptions","text":"Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Custom Exceptions"},{"location":"php/gdpr/","text":"General Data Protection Regulation (GDPR) # Introduction # The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com. Including Data in the Export # The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property. Example code # <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } } $data # Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app. $exportUserProperties # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content. $exportUserPropertiesIfNotEmpty # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output. $exportUserOptionSettings # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content. $exportUserOptionSettingsIfNotEmpty # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. $ipAddresses # List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported. $skipUserOptions # All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"GDPR"},{"location":"php/gdpr/#general-data-protection-regulation-gdpr","text":"","title":"General Data Protection Regulation (GDPR)"},{"location":"php/gdpr/#introduction","text":"The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com.","title":"Introduction"},{"location":"php/gdpr/#including-data-in-the-export","text":"The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property.","title":"Including Data in the Export"},{"location":"php/gdpr/#example-code","text":"<? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } }","title":"Example code"},{"location":"php/gdpr/#data","text":"Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app.","title":"$data"},{"location":"php/gdpr/#exportuserproperties","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content.","title":"$exportUserProperties"},{"location":"php/gdpr/#exportuserpropertiesifnotempty","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output.","title":"$exportUserPropertiesIfNotEmpty"},{"location":"php/gdpr/#exportuseroptionsettings","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content.","title":"$exportUserOptionSettings"},{"location":"php/gdpr/#exportuseroptionsettingsifnotempty","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data.","title":"$exportUserOptionSettingsIfNotEmpty"},{"location":"php/gdpr/#ipaddresses","text":"List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported.","title":"$ipAddresses"},{"location":"php/gdpr/#skipuseroptions","text":"All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"$skipUserOptions"},{"location":"php/pages/","text":"Page Types # AbstractPage # The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow. Method Chain # __run() # This is the only method being invoked from the outside and starts the whole chain. readParameters() # Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters show() # Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show readData() # Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData assignVariables() # Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables AbstractForm # Extends the AbstractPage implementation with additional methods designed to handle form submissions properly. Method Chain # __run() # Inherited from AbstractPage. readParameters() # Inherited from AbstractPage. show() # Inherited from AbstractPage. submit() # The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit readFormParameters() # This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters validate() # Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate save() # Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save saved() # This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved readData() # Inherited from AbstractPage. assignVariables() # Inherited from AbstractPage.","title":"Pages"},{"location":"php/pages/#page-types","text":"","title":"Page Types"},{"location":"php/pages/#abstractpage","text":"The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow.","title":"AbstractPage"},{"location":"php/pages/#method-chain","text":"","title":"Method Chain"},{"location":"php/pages/#__run","text":"This is the only method being invoked from the outside and starts the whole chain.","title":"__run()"},{"location":"php/pages/#readparameters","text":"Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters","title":"readParameters()"},{"location":"php/pages/#show","text":"Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show","title":"show()"},{"location":"php/pages/#readdata","text":"Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData","title":"readData()"},{"location":"php/pages/#assignvariables","text":"Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables","title":"assignVariables()"},{"location":"php/pages/#abstractform","text":"Extends the AbstractPage implementation with additional methods designed to handle form submissions properly.","title":"AbstractForm"},{"location":"php/pages/#method-chain_1","text":"","title":"Method Chain"},{"location":"php/pages/#__run_1","text":"Inherited from AbstractPage.","title":"__run()"},{"location":"php/pages/#readparameters_1","text":"Inherited from AbstractPage.","title":"readParameters()"},{"location":"php/pages/#show_1","text":"Inherited from AbstractPage.","title":"show()"},{"location":"php/pages/#submit","text":"The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit","title":"submit()"},{"location":"php/pages/#readformparameters","text":"This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters","title":"readFormParameters()"},{"location":"php/pages/#validate","text":"Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate","title":"validate()"},{"location":"php/pages/#save","text":"Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save","title":"save()"},{"location":"php/pages/#saved","text":"This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved","title":"saved()"},{"location":"php/pages/#readdata_1","text":"Inherited from AbstractPage.","title":"readData()"},{"location":"php/pages/#assignvariables_1","text":"Inherited from AbstractPage.","title":"assignVariables()"},{"location":"php/api/caches/","text":"Caches # WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request. Understanding Caching # Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem. When to Use a Cache # It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild. When not to Use a Cache # I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"Caches"},{"location":"php/api/caches/#caches","text":"WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request.","title":"Caches"},{"location":"php/api/caches/#understanding-caching","text":"Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem.","title":"Understanding Caching"},{"location":"php/api/caches/#when-to-use-a-cache","text":"It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild.","title":"When to Use a Cache"},{"location":"php/api/caches/#when-not-to-use-a-cache","text":"I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"When not to Use a Cache"},{"location":"php/api/caches_persistent-caches/","text":"Persistent Caches # Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision. AbstractCacheBuilder # Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters ); getData(array $parameters = [], string $arrayIndex = ''): array # Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead. getMaxLifetime(): int # Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated. reset(array $parameters = []): void # Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() . rebuild(array $parameters): array # This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#persistent-caches","text":"Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#abstractcachebuilder","text":"Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters );","title":"AbstractCacheBuilder"},{"location":"php/api/caches_persistent-caches/#getdataarray-parameters-string-arrayindex-array","text":"Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead.","title":"getData(array $parameters = [], string $arrayIndex = ''): array"},{"location":"php/api/caches_persistent-caches/#getmaxlifetime-int","text":"Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated.","title":"getMaxLifetime(): int"},{"location":"php/api/caches_persistent-caches/#resetarray-parameters-void","text":"Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() .","title":"reset(array $parameters = []): void"},{"location":"php/api/caches_persistent-caches/#rebuildarray-parameters-array","text":"This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"rebuild(array $parameters): array"},{"location":"php/api/caches_runtime-caches/","text":"Runtime Caches # Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again. IRuntimeCache # Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ). Usage # <? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]); Example # <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#runtime-caches","text":"Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again.","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#iruntimecache","text":"Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ).","title":"IRuntimeCache"},{"location":"php/api/caches_runtime-caches/#usage","text":"<? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]);","title":"Usage"},{"location":"php/api/caches_runtime-caches/#example","text":"<? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Example"},{"location":"php/api/comments/","text":"Comments # User Group Options # You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above. Language Items # User Group Options # The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"Comments"},{"location":"php/api/comments/#comments","text":"","title":"Comments"},{"location":"php/api/comments/#user-group-options","text":"You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above.","title":"User Group Options"},{"location":"php/api/comments/#language-items","text":"","title":"Language Items"},{"location":"php/api/comments/#user-group-options_1","text":"The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"User Group Options"},{"location":"php/api/cronjobs/","text":"Cronjobs # Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob. Example # <? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } } ICronjob Interface # Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ). Executing Cronjobs Through CLI # Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Cronjobs"},{"location":"php/api/cronjobs/#cronjobs","text":"Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob.","title":"Cronjobs"},{"location":"php/api/cronjobs/#example","text":"<? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } }","title":"Example"},{"location":"php/api/cronjobs/#icronjob-interface","text":"Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ).","title":"ICronjob Interface"},{"location":"php/api/cronjobs/#executing-cronjobs-through-cli","text":"Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Executing Cronjobs Through CLI"},{"location":"php/api/event_list/","text":"Event List # Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name. WoltLab Suite Core # Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput * WoltLab Suite Forum # Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"Event List"},{"location":"php/api/event_list/#event-list","text":"Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name.","title":"Event List"},{"location":"php/api/event_list/#woltlab-suite-core","text":"Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput *","title":"WoltLab Suite Core"},{"location":"php/api/event_list/#woltlab-suite-forum","text":"Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"WoltLab Suite Forum"},{"location":"php/api/events/","text":"Events # WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here . Introductory Example # Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled. Listening to Events # In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty. Firing Events # If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards. Example: Using $parameters argument # Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } } Advanced Example: Additional Form Field # One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Events"},{"location":"php/api/events/#events","text":"WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here .","title":"Events"},{"location":"php/api/events/#introductory-example","text":"Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled.","title":"Introductory Example"},{"location":"php/api/events/#listening-to-events","text":"In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty.","title":"Listening to Events"},{"location":"php/api/events/#firing-events","text":"If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards.","title":"Firing Events"},{"location":"php/api/events/#example-using-parameters-argument","text":"Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } }","title":"Example: Using $parameters argument"},{"location":"php/api/events/#advanced-example-additional-form-field","text":"One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Advanced Example: Additional Form Field"},{"location":"php/api/package_installation_plugins/","text":"Package Installation Plugins # A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation. AbstractPackageInstallationPlugin # Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes. Class Members # Package Installation Plugins have a few notable class members easing your work: $installation # This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() . $application # Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored. AbstractXMLPackageInstallationPlugin # AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data. Class Members # $className # Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects. $tagName # Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object. prepareImport(array $data) # The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ]; validateImport(array $data) # The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid. findExistingItem(array $data) # The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query. 2.5.3. Example # <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ]; handleDelete(array $items) # The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); } postImport() # Allows you to (optionally) run additionally actions after all elements were processed. AbstractOptionPackageInstallationPlugin # AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options Differences to AbstractXMLPackageInstallationPlugin # $reservedTags # $reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#package-installation-plugins","text":"A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#abstractpackageinstallationplugin","text":"Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes.","title":"AbstractPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members","text":"Package Installation Plugins have a few notable class members easing your work:","title":"Class Members"},{"location":"php/api/package_installation_plugins/#installation","text":"This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() .","title":"$installation"},{"location":"php/api/package_installation_plugins/#application","text":"Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored.","title":"$application"},{"location":"php/api/package_installation_plugins/#abstractxmlpackageinstallationplugin","text":"AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data.","title":"AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members_1","text":"","title":"Class Members"},{"location":"php/api/package_installation_plugins/#classname","text":"Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects.","title":"$className"},{"location":"php/api/package_installation_plugins/#tagname","text":"Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object.","title":"$tagName"},{"location":"php/api/package_installation_plugins/#prepareimportarray-data","text":"The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ];","title":"prepareImport(array $data)"},{"location":"php/api/package_installation_plugins/#validateimportarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid.","title":"validateImport(array $data)"},{"location":"php/api/package_installation_plugins/#findexistingitemarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query.","title":"findExistingItem(array $data)"},{"location":"php/api/package_installation_plugins/#253-example","text":"<? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ];","title":"2.5.3. Example"},{"location":"php/api/package_installation_plugins/#handledeletearray-items","text":"The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); }","title":"handleDelete(array $items)"},{"location":"php/api/package_installation_plugins/#postimport","text":"Allows you to (optionally) run additionally actions after all elements were processed.","title":"postImport()"},{"location":"php/api/package_installation_plugins/#abstractoptionpackageinstallationplugin","text":"AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options","title":"AbstractOptionPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#differences-to-abstractxmlpackageinstallationplugin","text":"","title":"Differences to AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#reservedtags","text":"$reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"$reservedTags"},{"location":"php/api/sitemaps/","text":"Sitemaps # This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/sitemaps/#sitemaps","text":"This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/user_activity_points/","text":"User Activity Points # Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_activity_points/#user-activity-points","text":"Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_notifications/","text":"User Notifications # WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis. objectType.xml # For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id. userNotificationEvent.xml # Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ). Firing Events # When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs ); Marking Notifications as Confirmed # In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"User Notifications"},{"location":"php/api/user_notifications/#user-notifications","text":"WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis.","title":"User Notifications"},{"location":"php/api/user_notifications/#objecttypexml","text":"For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id.","title":"objectType.xml"},{"location":"php/api/user_notifications/#usernotificationeventxml","text":"Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ).","title":"userNotificationEvent.xml"},{"location":"php/api/user_notifications/#firing-events","text":"When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs );","title":"Firing Events"},{"location":"php/api/user_notifications/#marking-notifications-as-confirmed","text":"In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"Marking Notifications as Confirmed"},{"location":"php/api/form_builder/dependencies/","text":"Form Node Dependencies # Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript. IFormFieldDependency # The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects. Default Dependencies # WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values. JavaScript Implementation # To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract . Examples # If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Dependencies"},{"location":"php/api/form_builder/dependencies/#form-node-dependencies","text":"Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript.","title":"Form Node Dependencies"},{"location":"php/api/form_builder/dependencies/#iformfielddependency","text":"The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects.","title":"IFormFieldDependency"},{"location":"php/api/form_builder/dependencies/#default-dependencies","text":"WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values.","title":"Default Dependencies"},{"location":"php/api/form_builder/dependencies/#javascript-implementation","text":"To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract .","title":"JavaScript Implementation"},{"location":"php/api/form_builder/dependencies/#examples","text":"If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Examples"},{"location":"php/api/form_builder/form_fields/","text":"Form Builder Fields # Abstract Form Fields # The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes. AbstractFormField # AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class. AbstractNumericFormField # AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any . General Form Fields # The following form fields are general reusable fields without any underlying context. BooleanFormField # BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField . CheckboxFormField # Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox. ClassNameFormField # ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively). DateFormField # DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled. DescriptionFormField # DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label. EmailFormField # EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField . FloatFormField # FloatFormField is an implementation of AbstractNumericFormField for floating point numbers. IconFormField # IconFormField is a form field to select a FontAwesome icon. IntegerFormField # IntegerFormField is an implementation of AbstractNumericFormField for integers. IsDisabledFormField # IsDisabledFormField is a boolean form field with isDisabled as the default id. ItemListFormField # ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key. MultilineTextFormField # MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered. MultipleSelectionFormField # MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value. RadioButtonFormField # RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField . RatingFormField # RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange . ShowOrderFormField # ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering. SingleSelectionFormField # SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value. SortOrderFormField # SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending . TextFormField # TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField . TitleFormField # TitleFormField is a text form field with title as the default id and wcf.global.title as the default label. UrlFormField # UrlFormField is a text form field whose values are checked via Url::is() . Specific Fields # The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation. AclFormField # AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately. ButtonFormField # Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked. CaptchaFormField # CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method. ContentLanguageFormField # ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField . LabelFormField # LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used. OptionFormField # OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options . SimpleAclFormField # SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. SingleMediaSelectionFormField # SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set. TagFormField # TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key. UploadFormField # UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed. Provide value from database object # To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files. Process files # To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded. UserFormField # UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method. UserPasswordField # Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField UserGroupOptionFormField # UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions . UsernameFormField # UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length. Wysiwyg form container # To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container. WysiwygAttachmentFormField # WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments. WysiwygPollFormContainer # WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built. WysiwygSmileyFormContainer # WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category. WysiwygSmileyFormNode # WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer . Example # The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes () WysiwygFormField # WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. TWysiwygFormNode # All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id. Single-Use Form Fields # The following form fields are specific for certain forms and hardly reusable in other contexts. BBCodeAttributesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode. DevtoolsProjectExcludedPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project. DevtoolsProjectInstructionsFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project. DevtoolsProjectOptionalPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project. DevtoolsProjectRequiredPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"Fields"},{"location":"php/api/form_builder/form_fields/#form-builder-fields","text":"","title":"Form Builder Fields"},{"location":"php/api/form_builder/form_fields/#abstract-form-fields","text":"The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes.","title":"Abstract Form Fields"},{"location":"php/api/form_builder/form_fields/#abstractformfield","text":"AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class.","title":"AbstractFormField"},{"location":"php/api/form_builder/form_fields/#abstractnumericformfield","text":"AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any .","title":"AbstractNumericFormField"},{"location":"php/api/form_builder/form_fields/#general-form-fields","text":"The following form fields are general reusable fields without any underlying context.","title":"General Form Fields"},{"location":"php/api/form_builder/form_fields/#booleanformfield","text":"BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField .","title":"BooleanFormField"},{"location":"php/api/form_builder/form_fields/#checkboxformfield","text":"Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox.","title":"CheckboxFormField"},{"location":"php/api/form_builder/form_fields/#classnameformfield","text":"ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively).","title":"ClassNameFormField"},{"location":"php/api/form_builder/form_fields/#dateformfield","text":"DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled.","title":"DateFormField"},{"location":"php/api/form_builder/form_fields/#descriptionformfield","text":"DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label.","title":"DescriptionFormField"},{"location":"php/api/form_builder/form_fields/#emailformfield","text":"EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField .","title":"EmailFormField"},{"location":"php/api/form_builder/form_fields/#floatformfield","text":"FloatFormField is an implementation of AbstractNumericFormField for floating point numbers.","title":"FloatFormField"},{"location":"php/api/form_builder/form_fields/#iconformfield","text":"IconFormField is a form field to select a FontAwesome icon.","title":"IconFormField"},{"location":"php/api/form_builder/form_fields/#integerformfield","text":"IntegerFormField is an implementation of AbstractNumericFormField for integers.","title":"IntegerFormField"},{"location":"php/api/form_builder/form_fields/#isdisabledformfield","text":"IsDisabledFormField is a boolean form field with isDisabled as the default id.","title":"IsDisabledFormField"},{"location":"php/api/form_builder/form_fields/#itemlistformfield","text":"ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key.","title":"ItemListFormField"},{"location":"php/api/form_builder/form_fields/#multilinetextformfield","text":"MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered.","title":"MultilineTextFormField"},{"location":"php/api/form_builder/form_fields/#multipleselectionformfield","text":"MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value.","title":"MultipleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#radiobuttonformfield","text":"RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField .","title":"RadioButtonFormField"},{"location":"php/api/form_builder/form_fields/#ratingformfield","text":"RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange .","title":"RatingFormField"},{"location":"php/api/form_builder/form_fields/#showorderformfield","text":"ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering.","title":"ShowOrderFormField"},{"location":"php/api/form_builder/form_fields/#singleselectionformfield","text":"SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value.","title":"SingleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#sortorderformfield","text":"SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending .","title":"SortOrderFormField"},{"location":"php/api/form_builder/form_fields/#textformfield","text":"TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField .","title":"TextFormField"},{"location":"php/api/form_builder/form_fields/#titleformfield","text":"TitleFormField is a text form field with title as the default id and wcf.global.title as the default label.","title":"TitleFormField"},{"location":"php/api/form_builder/form_fields/#urlformfield","text":"UrlFormField is a text form field whose values are checked via Url::is() .","title":"UrlFormField"},{"location":"php/api/form_builder/form_fields/#specific-fields","text":"The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation.","title":"Specific Fields"},{"location":"php/api/form_builder/form_fields/#aclformfield","text":"AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately.","title":"AclFormField"},{"location":"php/api/form_builder/form_fields/#buttonformfield","text":"Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked.","title":"ButtonFormField"},{"location":"php/api/form_builder/form_fields/#captchaformfield","text":"CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method.","title":"CaptchaFormField"},{"location":"php/api/form_builder/form_fields/#contentlanguageformfield","text":"ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField .","title":"ContentLanguageFormField"},{"location":"php/api/form_builder/form_fields/#labelformfield","text":"LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used.","title":"LabelFormField"},{"location":"php/api/form_builder/form_fields/#optionformfield","text":"OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options .","title":"OptionFormField"},{"location":"php/api/form_builder/form_fields/#simpleaclformfield","text":"SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"SimpleAclFormField"},{"location":"php/api/form_builder/form_fields/#singlemediaselectionformfield","text":"SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set.","title":"SingleMediaSelectionFormField"},{"location":"php/api/form_builder/form_fields/#tagformfield","text":"TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key.","title":"TagFormField"},{"location":"php/api/form_builder/form_fields/#uploadformfield","text":"UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed.","title":"UploadFormField"},{"location":"php/api/form_builder/form_fields/#provide-value-from-database-object","text":"To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files.","title":"Provide value from database object"},{"location":"php/api/form_builder/form_fields/#process-files","text":"To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded.","title":"Process files"},{"location":"php/api/form_builder/form_fields/#userformfield","text":"UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method.","title":"UserFormField"},{"location":"php/api/form_builder/form_fields/#userpasswordfield","text":"Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField","title":"UserPasswordField"},{"location":"php/api/form_builder/form_fields/#usergroupoptionformfield","text":"UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions .","title":"UserGroupOptionFormField"},{"location":"php/api/form_builder/form_fields/#usernameformfield","text":"UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length.","title":"UsernameFormField"},{"location":"php/api/form_builder/form_fields/#wysiwyg-form-container","text":"To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container.","title":"Wysiwyg form container"},{"location":"php/api/form_builder/form_fields/#wysiwygattachmentformfield","text":"WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments.","title":"WysiwygAttachmentFormField"},{"location":"php/api/form_builder/form_fields/#wysiwygpollformcontainer","text":"WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built.","title":"WysiwygPollFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformcontainer","text":"WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category.","title":"WysiwygSmileyFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformnode","text":"WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer .","title":"WysiwygSmileyFormNode"},{"location":"php/api/form_builder/form_fields/#example","text":"The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes ()","title":"Example"},{"location":"php/api/form_builder/form_fields/#wysiwygformfield","text":"WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"WysiwygFormField"},{"location":"php/api/form_builder/form_fields/#twysiwygformnode","text":"All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id.","title":"TWysiwygFormNode"},{"location":"php/api/form_builder/form_fields/#single-use-form-fields","text":"The following form fields are specific for certain forms and hardly reusable in other contexts.","title":"Single-Use Form Fields"},{"location":"php/api/form_builder/form_fields/#bbcodeattributesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode.","title":"BBCodeAttributesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectexcludedpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project.","title":"DevtoolsProjectExcludedPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectinstructionsformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project.","title":"DevtoolsProjectInstructionsFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectoptionalpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project.","title":"DevtoolsProjectOptionalPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectrequiredpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"DevtoolsProjectRequiredPackagesFormField"},{"location":"php/api/form_builder/overview/","text":"Form Builder # Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar. Advantages of Form Builder # WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form. Form Builder Components # Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems. AbstractFormBuilderForm # To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object. DialogFormDocument # Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"Overview"},{"location":"php/api/form_builder/overview/#form-builder","text":"Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar.","title":"Form Builder"},{"location":"php/api/form_builder/overview/#advantages-of-form-builder","text":"WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form.","title":"Advantages of Form Builder"},{"location":"php/api/form_builder/overview/#form-builder-components","text":"Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems.","title":"Form Builder Components"},{"location":"php/api/form_builder/overview/#abstractformbuilderform","text":"To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object.","title":"AbstractFormBuilderForm"},{"location":"php/api/form_builder/overview/#dialogformdocument","text":"Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"DialogFormDocument"},{"location":"php/api/form_builder/structure/","text":"Structure of Form Builder # Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining. Form Nodes # IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label. IFormNode # IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods. IFormChildNode # IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() . IFormParentNode # IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children. IFormElement # IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element. IObjectTypeFormNode # IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods. CustomFormNode # CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used. TemplateFormNode # TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template. Form Document # A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable. Form Button # A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element. Form Container # A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container. Form Field # A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here . Form Field Interfaces and Traits # WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields: IAttributeFormField # Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used. IAutoCompleteFormField # Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens. IAutoFocusFormField # IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods. ICssClassFormField # Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods. IFileFormField # IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ). IFilterableSelectionFormField # IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods. II18nFormField # II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() . IImmutableFormField # IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable. IInputModeFormField # Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods. IMaximumFormField # IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually. IMaximumLengthFormField # IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() . IMinimumFormField # IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually. IMinimumLengthFormField # IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() . IMultipleFormField # IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually. INullableFormField # INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable. IPackagesFormField # IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods. IPatternFormField # Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods. IPlaceholderFormField # IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods. ISelectionFormField # ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods. ISuffixedFormField # ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods. TDefaultIdFormField # Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() . Displaying Forms # The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Structure"},{"location":"php/api/form_builder/structure/#structure-of-form-builder","text":"Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining.","title":"Structure of Form Builder"},{"location":"php/api/form_builder/structure/#form-nodes","text":"IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label.","title":"Form Nodes"},{"location":"php/api/form_builder/structure/#iformnode","text":"IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods.","title":"IFormNode"},{"location":"php/api/form_builder/structure/#iformchildnode","text":"IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() .","title":"IFormChildNode"},{"location":"php/api/form_builder/structure/#iformparentnode","text":"IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children.","title":"IFormParentNode"},{"location":"php/api/form_builder/structure/#iformelement","text":"IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element.","title":"IFormElement"},{"location":"php/api/form_builder/structure/#iobjecttypeformnode","text":"IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods.","title":"IObjectTypeFormNode"},{"location":"php/api/form_builder/structure/#customformnode","text":"CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used.","title":"CustomFormNode"},{"location":"php/api/form_builder/structure/#templateformnode","text":"TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template.","title":"TemplateFormNode"},{"location":"php/api/form_builder/structure/#form-document","text":"A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable.","title":"Form Document"},{"location":"php/api/form_builder/structure/#form-button","text":"A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element.","title":"Form Button"},{"location":"php/api/form_builder/structure/#form-container","text":"A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container.","title":"Form Container"},{"location":"php/api/form_builder/structure/#form-field","text":"A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here .","title":"Form Field"},{"location":"php/api/form_builder/structure/#form-field-interfaces-and-traits","text":"WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields:","title":"Form Field Interfaces and Traits"},{"location":"php/api/form_builder/structure/#iattributeformfield","text":"Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used.","title":"IAttributeFormField"},{"location":"php/api/form_builder/structure/#iautocompleteformfield","text":"Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens.","title":"IAutoCompleteFormField"},{"location":"php/api/form_builder/structure/#iautofocusformfield","text":"IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods.","title":"IAutoFocusFormField"},{"location":"php/api/form_builder/structure/#icssclassformfield","text":"Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods.","title":"ICssClassFormField"},{"location":"php/api/form_builder/structure/#ifileformfield","text":"IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ).","title":"IFileFormField"},{"location":"php/api/form_builder/structure/#ifilterableselectionformfield","text":"IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods.","title":"IFilterableSelectionFormField"},{"location":"php/api/form_builder/structure/#ii18nformfield","text":"II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() .","title":"II18nFormField"},{"location":"php/api/form_builder/structure/#iimmutableformfield","text":"IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable.","title":"IImmutableFormField"},{"location":"php/api/form_builder/structure/#iinputmodeformfield","text":"Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods.","title":"IInputModeFormField"},{"location":"php/api/form_builder/structure/#imaximumformfield","text":"IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually.","title":"IMaximumFormField"},{"location":"php/api/form_builder/structure/#imaximumlengthformfield","text":"IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() .","title":"IMaximumLengthFormField"},{"location":"php/api/form_builder/structure/#iminimumformfield","text":"IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually.","title":"IMinimumFormField"},{"location":"php/api/form_builder/structure/#iminimumlengthformfield","text":"IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() .","title":"IMinimumLengthFormField"},{"location":"php/api/form_builder/structure/#imultipleformfield","text":"IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually.","title":"IMultipleFormField"},{"location":"php/api/form_builder/structure/#inullableformfield","text":"INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable.","title":"INullableFormField"},{"location":"php/api/form_builder/structure/#ipackagesformfield","text":"IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods.","title":"IPackagesFormField"},{"location":"php/api/form_builder/structure/#ipatternformfield","text":"Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods.","title":"IPatternFormField"},{"location":"php/api/form_builder/structure/#iplaceholderformfield","text":"IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods.","title":"IPlaceholderFormField"},{"location":"php/api/form_builder/structure/#iselectionformfield","text":"ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods.","title":"ISelectionFormField"},{"location":"php/api/form_builder/structure/#isuffixedformfield","text":"ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods.","title":"ISuffixedFormField"},{"location":"php/api/form_builder/structure/#tdefaultidformfield","text":"Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() .","title":"TDefaultIdFormField"},{"location":"php/api/form_builder/structure/#displaying-forms","text":"The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Displaying Forms"},{"location":"php/api/form_builder/validation_data/","text":"Form Validation and Form Data # Form Validation # Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface. IFormFieldValidationError / FormFieldValidationError # IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method. IFormFieldValidator / FormFieldValidator # IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method. Form Data # After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler. IFormDataHandler / FormDataHandler # IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly. IFormDataProcessor / DefaultFormDataProcessor # IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array. Additional Data Processors # CustomFormDataProcessor # As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged. VoidFormDataProcessor # Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"Validation and Data"},{"location":"php/api/form_builder/validation_data/#form-validation-and-form-data","text":"","title":"Form Validation and Form Data"},{"location":"php/api/form_builder/validation_data/#form-validation","text":"Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface.","title":"Form Validation"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidationerror-formfieldvalidationerror","text":"IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method.","title":"IFormFieldValidationError / FormFieldValidationError"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidator-formfieldvalidator","text":"IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method.","title":"IFormFieldValidator / FormFieldValidator"},{"location":"php/api/form_builder/validation_data/#form-data","text":"After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler.","title":"Form Data"},{"location":"php/api/form_builder/validation_data/#iformdatahandler-formdatahandler","text":"IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly.","title":"IFormDataHandler / FormDataHandler"},{"location":"php/api/form_builder/validation_data/#iformdataprocessor-defaultformdataprocessor","text":"IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array.","title":"IFormDataProcessor / DefaultFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#additional-data-processors","text":"","title":"Additional Data Processors"},{"location":"php/api/form_builder/validation_data/#customformdataprocessor","text":"As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged.","title":"CustomFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#voidformdataprocessor","text":"Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"VoidFormDataProcessor"},{"location":"tutorial/series/overview/","text":"Tutorial Series # In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Overview"},{"location":"tutorial/series/overview/#tutorial-series","text":"In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Tutorial Series"},{"location":"tutorial/series/part_1/","text":"Tutorial Series Part 1: Base Structure # In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions. Package Functionality # The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml Person Modeling # Database Table # As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL ); Database Object # Person # In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class. PersonAction # <? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP. PersonEditor # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name. PersonList # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class. ACP # Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu. ACP Menu # We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value. People List # To list the people in the ACP, we need a PersonListPage class and a personList template. PersonListPage # <? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns. personList.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people. Person Add Form # Like the person list, the form to add new people requires a controller class and a template. PersonAddForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases. personAdd.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted. Person Edit Form # As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing. PersonEditForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\" Frontend # For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu. page.xml # First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page . menuItem.xml # Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered. People List # As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories. PersonListPage # <? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default. personList.tpl # { capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events. userGroupOption.xml # We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 . package.xml # Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"Part 1"},{"location":"tutorial/series/part_1/#tutorial-series-part-1-base-structure","text":"In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions.","title":"Tutorial Series Part 1: Base Structure"},{"location":"tutorial/series/part_1/#package-functionality","text":"The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_1/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates .","title":"Used Components"},{"location":"tutorial/series/part_1/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml","title":"Package Structure"},{"location":"tutorial/series/part_1/#person-modeling","text":"","title":"Person Modeling"},{"location":"tutorial/series/part_1/#database-table","text":"As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL );","title":"Database Table"},{"location":"tutorial/series/part_1/#database-object","text":"","title":"Database Object"},{"location":"tutorial/series/part_1/#person","text":"In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class.","title":"Person"},{"location":"tutorial/series/part_1/#personaction","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP.","title":"PersonAction"},{"location":"tutorial/series/part_1/#personeditor","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name.","title":"PersonEditor"},{"location":"tutorial/series/part_1/#personlist","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class.","title":"PersonList"},{"location":"tutorial/series/part_1/#acp","text":"Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu.","title":"ACP"},{"location":"tutorial/series/part_1/#acp-menu","text":"We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value.","title":"ACP Menu"},{"location":"tutorial/series/part_1/#people-list","text":"To list the people in the ACP, we need a PersonListPage class and a personList template.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage","text":"<? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#person-add-form","text":"Like the person list, the form to add new people requires a controller class and a template.","title":"Person Add Form"},{"location":"tutorial/series/part_1/#personaddform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases.","title":"PersonAddForm"},{"location":"tutorial/series/part_1/#personaddtpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted.","title":"personAdd.tpl"},{"location":"tutorial/series/part_1/#person-edit-form","text":"As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing.","title":"Person Edit Form"},{"location":"tutorial/series/part_1/#personeditform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\"","title":"PersonEditForm"},{"location":"tutorial/series/part_1/#frontend","text":"For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu.","title":"Frontend"},{"location":"tutorial/series/part_1/#pagexml","text":"First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page .","title":"page.xml"},{"location":"tutorial/series/part_1/#menuitemxml","text":"Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered.","title":"menuItem.xml"},{"location":"tutorial/series/part_1/#people-list_1","text":"As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage_1","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl_1","text":"{ capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#usergroupoptionxml","text":"We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 .","title":"userGroupOption.xml"},{"location":"tutorial/series/part_1/#packagexml","text":"Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_2/","text":"Part 2: Event Listeners and Template Listeners # In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date. Package Functionality # The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl Extending Person Model ( install.sql ) # The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday . Setting Birthday in ACP # To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below . Adding Birthday Table Column in ACP # To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td> Adding Birthday in Front End # In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if } templateListener.xml # The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template. eventListener.xml # There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data> package.xml # The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"Part 2"},{"location":"tutorial/series/part_2/#part-2-event-listeners-and-template-listeners","text":"In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date.","title":"Part 2: Event Listeners and Template Listeners"},{"location":"tutorial/series/part_2/#package-functionality","text":"The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_2/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events .","title":"Used Components"},{"location":"tutorial/series/part_2/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl","title":"Package Structure"},{"location":"tutorial/series/part_2/#extending-person-model-installsql","text":"The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday .","title":"Extending Person Model (install.sql)"},{"location":"tutorial/series/part_2/#setting-birthday-in-acp","text":"To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below .","title":"Setting Birthday in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-table-column-in-acp","text":"To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td>","title":"Adding Birthday Table Column in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-in-front-end","text":"In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if }","title":"Adding Birthday in Front End"},{"location":"tutorial/series/part_2/#templatelistenerxml","text":"The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template.","title":"templateListener.xml"},{"location":"tutorial/series/part_2/#eventlistenerxml","text":"There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data>","title":"eventListener.xml"},{"location":"tutorial/series/part_2/#packagexml","text":"The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_3/","text":"Tutorial Series Part 3: Person Page and Comments # In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality. Package Functionality # In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages Used Components # In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler. Package Structure # The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub . Runtime Cache # To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; } Comments # To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments. Person Page # PersonPage # <? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments. person.tpl # {capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix. page.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent. PersonPageHandler # <? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"Part 3"},{"location":"tutorial/series/part_3/#tutorial-series-part-3-person-page-and-comments","text":"In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality.","title":"Tutorial Series Part 3: Person Page and Comments"},{"location":"tutorial/series/part_3/#package-functionality","text":"In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages","title":"Package Functionality"},{"location":"tutorial/series/part_3/#used-components","text":"In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler.","title":"Used Components"},{"location":"tutorial/series/part_3/#package-structure","text":"The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub .","title":"Package Structure"},{"location":"tutorial/series/part_3/#runtime-cache","text":"To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; }","title":"Runtime Cache"},{"location":"tutorial/series/part_3/#comments","text":"To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments.","title":"Comments"},{"location":"tutorial/series/part_3/#person-page","text":"","title":"Person Page"},{"location":"tutorial/series/part_3/#personpage","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments.","title":"PersonPage"},{"location":"tutorial/series/part_3/#persontpl","text":"{capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix.","title":"person.tpl"},{"location":"tutorial/series/part_3/#pagexml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent.","title":"page.xml"},{"location":"tutorial/series/part_3/#personpagehandler","text":"<? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"PersonPageHandler"},{"location":"view/css/","text":"CSS # SCSS and CSS # SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code. File Location # Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss . Variables # You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} . Linking images # Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash. Media Breakpoints # Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } } Available Breakpoints # Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px) Asset Preloading # WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"CSS"},{"location":"view/css/#css","text":"","title":"CSS"},{"location":"view/css/#scss-and-css","text":"SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code.","title":"SCSS and CSS"},{"location":"view/css/#file-location","text":"Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss .","title":"File Location"},{"location":"view/css/#variables","text":"You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} .","title":"Variables"},{"location":"view/css/#linking-images","text":"Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash.","title":"Linking images"},{"location":"view/css/#media-breakpoints","text":"Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } }","title":"Media Breakpoints"},{"location":"view/css/#available-breakpoints","text":"Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px)","title":"Available Breakpoints"},{"location":"view/css/#asset-preloading","text":"WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"Asset Preloading"},{"location":"view/languages-naming-conventions/","text":"Language Naming Conventions # This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments Forms # Fields # If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description . Error Texts # If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items. Naming Conventions # If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type. Confirmation messages # If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items. Type-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen? English # Do you really want delete the {element type}? Example: Do you really want delete the icon? Object-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen? English # Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>? User Group Options # Comments # German # group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren English # group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#language-naming-conventions","text":"This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#forms","text":"","title":"Forms"},{"location":"view/languages-naming-conventions/#fields","text":"If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description .","title":"Fields"},{"location":"view/languages-naming-conventions/#error-texts","text":"If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items.","title":"Error Texts"},{"location":"view/languages-naming-conventions/#naming-conventions","text":"If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type.","title":"Naming Conventions"},{"location":"view/languages-naming-conventions/#confirmation-messages","text":"If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items.","title":"Confirmation messages"},{"location":"view/languages-naming-conventions/#type-specific-deletion-confirmation-message","text":"","title":"Type-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english","text":"Do you really want delete the {element type}? Example: Do you really want delete the icon?","title":"English"},{"location":"view/languages-naming-conventions/#object-specific-deletion-confirmation-message","text":"","title":"Object-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german_1","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english_1","text":"Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>?","title":"English"},{"location":"view/languages-naming-conventions/#user-group-options","text":"","title":"User Group Options"},{"location":"view/languages-naming-conventions/#comments","text":"","title":"Comments"},{"location":"view/languages-naming-conventions/#german_2","text":"group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren","title":"German"},{"location":"view/languages-naming-conventions/#english_2","text":"group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"English"},{"location":"view/languages/","text":"Languages # WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items . Special Phrases # wcf.date.dateFormat # Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation. wcf.date.timeFormat # Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters. wcf.date.firstDayOfTheWeek # Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday wcf.global.pageDirection - RTL support # Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"Languages"},{"location":"view/languages/#languages","text":"WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items .","title":"Languages"},{"location":"view/languages/#special-phrases","text":"","title":"Special Phrases"},{"location":"view/languages/#wcfdatedateformat","text":"Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation.","title":"wcf.date.dateFormat"},{"location":"view/languages/#wcfdatetimeformat","text":"Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters.","title":"wcf.date.timeFormat"},{"location":"view/languages/#wcfdatefirstdayoftheweek","text":"Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday","title":"wcf.date.firstDayOfTheWeek"},{"location":"view/languages/#wcfglobalpagedirection-rtl-support","text":"Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"wcf.global.pageDirection - RTL support"},{"location":"view/template-plugins/","text":"Template Plugins # 5.3+ anchor # The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink . 5.3+ anchorAttributes # anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default append # If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array. assign # New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } capture # In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture } concat # concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * } counter # counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default 5.4+ csrfToken # {csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details. currency # currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * } cycle # cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter date # date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date } 3.1+ dateInterval # dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set) encodeJS # encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script> encodeJSON # encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } ' escapeCDATA # escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]> event # event provides extension points in templates that template listeners can use. { event name = 'foo' } fetch # fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * } filesizeBinary # filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary } filesize # filesize formats the filesize using filesize (in bytes). { $filesize | filesize } hascontent # In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent } htmlCheckboxes # htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options htmlOptions # htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element. implode # implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration 5.2+ ipSearch # ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch } 3.0+ js # js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * } 5.3+ jslang # jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); lang # lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item. language # language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language } link # link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() . newlineToBreak # newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak } 3.0+ page # page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID ) pages # pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links plainTime # plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime } 5.3+ plural # plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer. prepend # If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array. shortUnit # shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * } smallpages # smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links. tableWordwrap # tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap } time # time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * } truncate # truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default 5.3+ user # user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"Template Plugins"},{"location":"view/template-plugins/#template-plugins","text":"","title":"Template Plugins"},{"location":"view/template-plugins/#53-anchor","text":"The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink .","title":"5.3+ anchor"},{"location":"view/template-plugins/#53-anchorattributes","text":"anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default","title":"5.3+ anchorAttributes"},{"location":"view/template-plugins/#append","text":"If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array.","title":"append"},{"location":"view/template-plugins/#assign","text":"New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * }","title":"assign"},{"location":"view/template-plugins/#capture","text":"In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture }","title":"capture"},{"location":"view/template-plugins/#concat","text":"concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * }","title":"concat"},{"location":"view/template-plugins/#counter","text":"counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default","title":"counter"},{"location":"view/template-plugins/#54-csrftoken","text":"{csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details.","title":"5.4+ csrfToken"},{"location":"view/template-plugins/#currency","text":"currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * }","title":"currency"},{"location":"view/template-plugins/#cycle","text":"cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter","title":"cycle"},{"location":"view/template-plugins/#date","text":"date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date }","title":"date"},{"location":"view/template-plugins/#31-dateinterval","text":"dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set)","title":"3.1+ dateInterval"},{"location":"view/template-plugins/#encodejs","text":"encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script>","title":"encodeJS"},{"location":"view/template-plugins/#encodejson","text":"encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } '","title":"encodeJSON"},{"location":"view/template-plugins/#escapecdata","text":"escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]>","title":"escapeCDATA"},{"location":"view/template-plugins/#event","text":"event provides extension points in templates that template listeners can use. { event name = 'foo' }","title":"event"},{"location":"view/template-plugins/#fetch","text":"fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * }","title":"fetch"},{"location":"view/template-plugins/#filesizebinary","text":"filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary }","title":"filesizeBinary"},{"location":"view/template-plugins/#filesize","text":"filesize formats the filesize using filesize (in bytes). { $filesize | filesize }","title":"filesize"},{"location":"view/template-plugins/#hascontent","text":"In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent }","title":"hascontent"},{"location":"view/template-plugins/#htmlcheckboxes","text":"htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options","title":"htmlCheckboxes"},{"location":"view/template-plugins/#htmloptions","text":"htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element.","title":"htmlOptions"},{"location":"view/template-plugins/#implode","text":"implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration","title":"implode"},{"location":"view/template-plugins/#52-ipsearch","text":"ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch }","title":"5.2+ ipSearch"},{"location":"view/template-plugins/#30-js","text":"js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * }","title":"3.0+ js"},{"location":"view/template-plugins/#53-jslang","text":"jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } );","title":"5.3+ jslang"},{"location":"view/template-plugins/#lang","text":"lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item.","title":"lang"},{"location":"view/template-plugins/#language","text":"language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language }","title":"language"},{"location":"view/template-plugins/#link","text":"link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() .","title":"link"},{"location":"view/template-plugins/#newlinetobreak","text":"newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak }","title":"newlineToBreak"},{"location":"view/template-plugins/#30-page","text":"page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID )","title":"3.0+ page"},{"location":"view/template-plugins/#pages","text":"pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links","title":"pages"},{"location":"view/template-plugins/#plaintime","text":"plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime }","title":"plainTime"},{"location":"view/template-plugins/#53-plural","text":"plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer.","title":"5.3+ plural"},{"location":"view/template-plugins/#prepend","text":"If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array.","title":"prepend"},{"location":"view/template-plugins/#shortunit","text":"shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * }","title":"shortUnit"},{"location":"view/template-plugins/#smallpages","text":"smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links.","title":"smallpages"},{"location":"view/template-plugins/#tablewordwrap","text":"tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap }","title":"tableWordwrap"},{"location":"view/template-plugins/#time","text":"time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * }","title":"time"},{"location":"view/template-plugins/#truncate","text":"truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default","title":"truncate"},{"location":"view/template-plugins/#53-user","text":"user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"5.3+ user"},{"location":"view/templates/","text":"Templates # Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore. Template Types and Conventions # WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal. Installing Templates # Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages. Base Templates # Frontend # { include file = 'header' } { * content * } { include file = 'footer' } Backend # { include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page. Common Template Components # Forms # For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> Tab Menus # <div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div> Template Scripting # Template Variables # Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]); Modifiers # If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed. System Template Variable # The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend). Comments # Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template. Conditions # Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if } Foreach Loops # Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach } Including Other Templates # To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' } Template Plugins # An overview of all available template plugins can be found here .","title":"Templates"},{"location":"view/templates/#templates","text":"Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore.","title":"Templates"},{"location":"view/templates/#template-types-and-conventions","text":"WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal.","title":"Template Types and Conventions"},{"location":"view/templates/#installing-templates","text":"Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages.","title":"Installing Templates"},{"location":"view/templates/#base-templates","text":"","title":"Base Templates"},{"location":"view/templates/#frontend","text":"{ include file = 'header' } { * content * } { include file = 'footer' }","title":"Frontend"},{"location":"view/templates/#backend","text":"{ include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page.","title":"Backend"},{"location":"view/templates/#common-template-components","text":"","title":"Common Template Components"},{"location":"view/templates/#forms","text":"For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form>","title":"Forms"},{"location":"view/templates/#tab-menus","text":"<div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div>","title":"Tab Menus"},{"location":"view/templates/#template-scripting","text":"","title":"Template Scripting"},{"location":"view/templates/#template-variables","text":"Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]);","title":"Template Variables"},{"location":"view/templates/#modifiers","text":"If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed.","title":"Modifiers"},{"location":"view/templates/#system-template-variable","text":"The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend).","title":"System Template Variable"},{"location":"view/templates/#comments","text":"Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template.","title":"Comments"},{"location":"view/templates/#conditions","text":"Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if }","title":"Conditions"},{"location":"view/templates/#foreach-loops","text":"Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach }","title":"Foreach Loops"},{"location":"view/templates/#including-other-templates","text":"To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' }","title":"Including Other Templates"},{"location":"view/templates/#template-plugins","text":"An overview of all available template plugins can be found here .","title":"Template Plugins"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"WoltLab Suite 5.4 Documentation # Introduction # This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more. About WoltLab Suite # WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"WoltLab Suite 5.4 Documentation"},{"location":"#woltlab-suite-54-documentation","text":"","title":"WoltLab Suite 5.4 Documentation"},{"location":"#introduction","text":"This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more.","title":"Introduction"},{"location":"#about-woltlab-suite","text":"WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"About WoltLab Suite"},{"location":"getting-started/","text":"Creating a simple package # Setup and Requirements # This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows The package.xml File # We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is. The PHP Class # The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all. The Template # Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance. The Page Definition # The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus. Building the Package # If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition. Installation # Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package! Developer Tools # This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed. Registering a Project # Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml . Synchronizing # The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed. Appendix # Template Guessing # The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Getting Started"},{"location":"getting-started/#creating-a-simple-package","text":"","title":"Creating a simple package"},{"location":"getting-started/#setup-and-requirements","text":"This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows","title":"Setup and Requirements"},{"location":"getting-started/#the-packagexml-file","text":"We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is.","title":"The package.xml File"},{"location":"getting-started/#the-php-class","text":"The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all.","title":"The PHP Class"},{"location":"getting-started/#the-template","text":"Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance.","title":"The Template"},{"location":"getting-started/#the-page-definition","text":"The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus.","title":"The Page Definition"},{"location":"getting-started/#building-the-package","text":"If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition.","title":"Building the Package"},{"location":"getting-started/#installation","text":"Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package!","title":"Installation"},{"location":"getting-started/#developer-tools","text":"This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed.","title":"Developer Tools"},{"location":"getting-started/#registering-a-project","text":"Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml .","title":"Registering a Project"},{"location":"getting-started/#synchronizing","text":"The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed.","title":"Synchronizing"},{"location":"getting-started/#appendix","text":"","title":"Appendix"},{"location":"getting-started/#template-guessing","text":"The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Template Guessing"},{"location":"javascript/code-snippets/","text":"Code Snippets - JavaScript API # This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function. ImageViewer # The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"Code Snippets"},{"location":"javascript/code-snippets/#code-snippets-javascript-api","text":"This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function.","title":"Code Snippets - JavaScript API"},{"location":"javascript/code-snippets/#imageviewer","text":"The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"ImageViewer"},{"location":"javascript/general-usage/","text":"General JavaScript Usage # The History of the Legacy API # The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base. Embedding JavaScript inside Templates # The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script > Including External JavaScript Files # The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about. Debug-Variants and Cache-Buster # Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The Accelerated Guest View (\"Tiny Builds\") # You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The {js} Template Plugin # The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"General Usage"},{"location":"javascript/general-usage/#general-javascript-usage","text":"","title":"General JavaScript Usage"},{"location":"javascript/general-usage/#the-history-of-the-legacy-api","text":"The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base.","title":"The History of the Legacy API"},{"location":"javascript/general-usage/#embedding-javascript-inside-templates","text":"The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script >","title":"Embedding JavaScript inside Templates"},{"location":"javascript/general-usage/#including-external-javascript-files","text":"The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about.","title":"Including External JavaScript Files"},{"location":"javascript/general-usage/#debug-variants-and-cache-buster","text":"Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"Debug-Variants and Cache-Buster"},{"location":"javascript/general-usage/#the-accelerated-guest-view-tiny-builds","text":"You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"The Accelerated Guest View (\"Tiny Builds\")"},{"location":"javascript/general-usage/#the-js-template-plugin","text":"The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"The {js} Template Plugin"},{"location":"javascript/helper-functions/","text":"JavaScript Helper Functions # Introduction # Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction. Elements # elCreate(tagName: string): Element # Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" ); elRemove(element: Element) # Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element ); elShow(element: Element) # Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" ); elHide(element: Element) # Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" ); elToggle(element: Element) # Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() . Attributes # elAttr(element: Element, attribute: string, value?: string): string # Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" ); elAttrBool(element: Element, attribute: string): boolean # Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish } elData(element: Element, attribute: string, value?: string): string # Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" ); elDataBool(element: Element, attribute: string): boolean # Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish } Selecting Elements # Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error. elById(id: string): Element | null # Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" ); elBySel(selector: string, context?: Element): Element | null # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" ); elBySelAll(selector: string, context?: Element, callback: (element: Element) => void): NodeList # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" ); Callback to Iterate Over Elements # elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element }); elClosest(element: Element, selector: string): Element | null # Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" ); Text Nodes # If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector. elByClass(className: string, context?: Element): NodeList # Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" ); elByTag(tagName: string, context?: Element): NodeList # Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" ); Utility Functions # `elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null`` # Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function. String Extensions # hashCode(): string # Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"Helper Functions"},{"location":"javascript/helper-functions/#javascript-helper-functions","text":"","title":"JavaScript Helper Functions"},{"location":"javascript/helper-functions/#introduction","text":"Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction.","title":"Introduction"},{"location":"javascript/helper-functions/#elements","text":"","title":"Elements"},{"location":"javascript/helper-functions/#elcreatetagname-string-element","text":"Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" );","title":"elCreate(tagName: string): Element"},{"location":"javascript/helper-functions/#elremoveelement-element","text":"Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element );","title":"elRemove(element: Element)"},{"location":"javascript/helper-functions/#elshowelement-element","text":"Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" );","title":"elShow(element: Element)"},{"location":"javascript/helper-functions/#elhideelement-element","text":"Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" );","title":"elHide(element: Element)"},{"location":"javascript/helper-functions/#eltoggleelement-element","text":"Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() .","title":"elToggle(element: Element)"},{"location":"javascript/helper-functions/#attributes","text":"","title":"Attributes"},{"location":"javascript/helper-functions/#elattrelement-element-attribute-string-value-string-string","text":"Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" );","title":"elAttr(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#elattrboolelement-element-attribute-string-boolean","text":"Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish }","title":"elAttrBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#eldataelement-element-attribute-string-value-string-string","text":"Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" );","title":"elData(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#eldataboolelement-element-attribute-string-boolean","text":"Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish }","title":"elDataBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#selecting-elements","text":"Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error.","title":"Selecting Elements"},{"location":"javascript/helper-functions/#elbyidid-string-element-null","text":"Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" );","title":"elById(id: string): Element | null"},{"location":"javascript/helper-functions/#elbyselselector-string-context-element-element-null","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" );","title":"elBySel(selector: string, context?: Element): Element | null"},{"location":"javascript/helper-functions/#elbyselallselector-string-context-element-callback-element-element-void-nodelist","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" );","title":"elBySelAll(selector: string, context?: Element, callback: (element: Element) =&gt; void): NodeList"},{"location":"javascript/helper-functions/#callback-to-iterate-over-elements","text":"elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element });","title":"Callback to Iterate Over Elements"},{"location":"javascript/helper-functions/#elclosestelement-element-selector-string-element-null","text":"Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" );","title":"elClosest(element: Element, selector: string): Element | null"},{"location":"javascript/helper-functions/#text-nodes","text":"If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector.","title":"Text Nodes"},{"location":"javascript/helper-functions/#elbyclassclassname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" );","title":"elByClass(className: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#elbytagtagname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" );","title":"elByTag(tagName: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#utility-functions","text":"","title":"Utility Functions"},{"location":"javascript/helper-functions/#elinnererrorelement-element-errormessage-string-ishtml-boolean-element-null","text":"Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function.","title":"`elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null``"},{"location":"javascript/helper-functions/#string-extensions","text":"","title":"String Extensions"},{"location":"javascript/helper-functions/#hashcode-string","text":"Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"hashCode(): string"},{"location":"javascript/legacy-api/","text":"Legacy JavaScript API # Introduction # The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API. Classes # Singletons # Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; }); Regular Classes # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); Inheritance # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); Ajax Requests # // App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; }); Phrases # < script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script > Event-Listener # < script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Legacy API"},{"location":"javascript/legacy-api/#legacy-javascript-api","text":"","title":"Legacy JavaScript API"},{"location":"javascript/legacy-api/#introduction","text":"The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API.","title":"Introduction"},{"location":"javascript/legacy-api/#classes","text":"","title":"Classes"},{"location":"javascript/legacy-api/#singletons","text":"Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; });","title":"Singletons"},{"location":"javascript/legacy-api/#regular-classes","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; });","title":"Regular Classes"},{"location":"javascript/legacy-api/#inheritance","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"Inheritance"},{"location":"javascript/legacy-api/#ajax-requests","text":"// App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; });","title":"Ajax Requests"},{"location":"javascript/legacy-api/#phrases","text":"< script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script >","title":"Phrases"},{"location":"javascript/legacy-api/#event-listener","text":"< script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Event-Listener"},{"location":"javascript/new-api_ajax/","text":"Ajax Requests - JavaScript API # Ajax inside Modules # The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed. _ajaxSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; }); Request Settings # The object returned by the aforementioned _ajaxSetup() callback can contain these values: data # Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters . contentType # Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty. responseType # Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess ! type # Defaults to POST . The HTTP Verb used for this request. url # Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true. withCredentials # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint. autoAbort # Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code. ignoreError # Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback. silent # Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator. includeRequestedWith # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint. failure # Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON. finalize # Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest . success # Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued. _ajaxSuccess() # This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests. _ajaxFailure() # Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed. Single Requests Without a Module # The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Ajax"},{"location":"javascript/new-api_ajax/#ajax-requests-javascript-api","text":"","title":"Ajax Requests - JavaScript API"},{"location":"javascript/new-api_ajax/#ajax-inside-modules","text":"The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed.","title":"Ajax inside Modules"},{"location":"javascript/new-api_ajax/#_ajaxsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; });","title":"_ajaxSetup()"},{"location":"javascript/new-api_ajax/#request-settings","text":"The object returned by the aforementioned _ajaxSetup() callback can contain these values:","title":"Request Settings"},{"location":"javascript/new-api_ajax/#data","text":"Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters .","title":"data"},{"location":"javascript/new-api_ajax/#contenttype","text":"Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty.","title":"contentType"},{"location":"javascript/new-api_ajax/#responsetype","text":"Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess !","title":"responseType"},{"location":"javascript/new-api_ajax/#type","text":"Defaults to POST . The HTTP Verb used for this request.","title":"type"},{"location":"javascript/new-api_ajax/#url","text":"Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true.","title":"url"},{"location":"javascript/new-api_ajax/#withcredentials","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint.","title":"withCredentials"},{"location":"javascript/new-api_ajax/#autoabort","text":"Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code.","title":"autoAbort"},{"location":"javascript/new-api_ajax/#ignoreerror","text":"Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback.","title":"ignoreError"},{"location":"javascript/new-api_ajax/#silent","text":"Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator.","title":"silent"},{"location":"javascript/new-api_ajax/#includerequestedwith","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint.","title":"includeRequestedWith"},{"location":"javascript/new-api_ajax/#failure","text":"Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON.","title":"failure"},{"location":"javascript/new-api_ajax/#finalize","text":"Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest .","title":"finalize"},{"location":"javascript/new-api_ajax/#success","text":"Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued.","title":"success"},{"location":"javascript/new-api_ajax/#_ajaxsuccess","text":"This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests.","title":"_ajaxSuccess()"},{"location":"javascript/new-api_ajax/#_ajaxfailure","text":"Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed.","title":"_ajaxFailure()"},{"location":"javascript/new-api_ajax/#single-requests-without-a-module","text":"The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Single Requests Without a Module"},{"location":"javascript/new-api_browser/","text":"Browser and Screen Sizes - JavaScript API # Ui/Screen # CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle. Supported Aliases # You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px) on(query: string, callbacks: Object): string # Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() . remove(query: string, uuid: string) # Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() . is(query: string): boolean # Tests if the provided media query currently matches and returns true on match. scrollDisable() # Temporarily prevents the page from being scrolled, until .scrollEnable() is called. scrollEnable() # Enables page scrolling again, unless another pending action has also prevented the page scrolling. Environment # The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks. browser(): string # Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default) platform(): string # Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"Browser and Screen Sizes"},{"location":"javascript/new-api_browser/#browser-and-screen-sizes-javascript-api","text":"","title":"Browser and Screen Sizes - JavaScript API"},{"location":"javascript/new-api_browser/#uiscreen","text":"CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle.","title":"Ui/Screen"},{"location":"javascript/new-api_browser/#supported-aliases","text":"You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px)","title":"Supported Aliases"},{"location":"javascript/new-api_browser/#onquery-string-callbacks-object-string","text":"Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() .","title":"on(query: string, callbacks: Object): string"},{"location":"javascript/new-api_browser/#removequery-string-uuid-string","text":"Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() .","title":"remove(query: string, uuid: string)"},{"location":"javascript/new-api_browser/#isquery-string-boolean","text":"Tests if the provided media query currently matches and returns true on match.","title":"is(query: string): boolean"},{"location":"javascript/new-api_browser/#scrolldisable","text":"Temporarily prevents the page from being scrolled, until .scrollEnable() is called.","title":"scrollDisable()"},{"location":"javascript/new-api_browser/#scrollenable","text":"Enables page scrolling again, unless another pending action has also prevented the page scrolling.","title":"scrollEnable()"},{"location":"javascript/new-api_browser/#environment","text":"The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks.","title":"Environment"},{"location":"javascript/new-api_browser/#browser-string","text":"Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default)","title":"browser(): string"},{"location":"javascript/new-api_browser/#platform-string","text":"Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"platform(): string"},{"location":"javascript/new-api_core/","text":"Core Modules and Functions - JavaScript API # A brief overview of common methods that may be useful when writing any module. Core # clone(object: Object): Object # Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true }); extend(base: Object, ...merge: Object[]): Object # Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false }); inherit(base: Object, target: Object, merge?: Object) # Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); isPlainObject(object: Object): boolean # Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false }); triggerEvent(element: Element, eventName: string) # Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); }); Language # add(key: string, value: string) # Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script > addObject(object: Object) # Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script > get(key: string, parameters?: Object): string # Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); }); StringUtil # escapeHTML(str: string): string # Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt; escapeRegExp(str: string): string # Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions. lcfirst(str: string): string # Makes a string's first character lowercase. ucfirst(str: string): string # Makes a string's first character uppercase. unescapeHTML(str: string): string # Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"Core Functions"},{"location":"javascript/new-api_core/#core-modules-and-functions-javascript-api","text":"A brief overview of common methods that may be useful when writing any module.","title":"Core Modules and Functions - JavaScript API"},{"location":"javascript/new-api_core/#core","text":"","title":"Core"},{"location":"javascript/new-api_core/#cloneobject-object-object","text":"Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true });","title":"clone(object: Object): Object"},{"location":"javascript/new-api_core/#extendbase-object-merge-object-object","text":"Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false });","title":"extend(base: Object, ...merge: Object[]): Object"},{"location":"javascript/new-api_core/#inheritbase-object-target-object-merge-object","text":"Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"inherit(base: Object, target: Object, merge?: Object)"},{"location":"javascript/new-api_core/#isplainobjectobject-object-boolean","text":"Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false });","title":"isPlainObject(object: Object): boolean"},{"location":"javascript/new-api_core/#triggereventelement-element-eventname-string","text":"Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); });","title":"triggerEvent(element: Element, eventName: string)"},{"location":"javascript/new-api_core/#language","text":"","title":"Language"},{"location":"javascript/new-api_core/#addkey-string-value-string","text":"Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script >","title":"add(key: string, value: string)"},{"location":"javascript/new-api_core/#addobjectobject-object","text":"Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script >","title":"addObject(object: Object)"},{"location":"javascript/new-api_core/#getkey-string-parameters-object-string","text":"Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); });","title":"get(key: string, parameters?: Object): string"},{"location":"javascript/new-api_core/#stringutil","text":"","title":"StringUtil"},{"location":"javascript/new-api_core/#escapehtmlstr-string-string","text":"Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt;","title":"escapeHTML(str: string): string"},{"location":"javascript/new-api_core/#escaperegexpstr-string-string","text":"Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions.","title":"escapeRegExp(str: string): string"},{"location":"javascript/new-api_core/#lcfirststr-string-string","text":"Makes a string's first character lowercase.","title":"lcfirst(str: string): string"},{"location":"javascript/new-api_core/#ucfirststr-string-string","text":"Makes a string's first character uppercase.","title":"ucfirst(str: string): string"},{"location":"javascript/new-api_core/#unescapehtmlstr-string-string","text":"Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"unescapeHTML(str: string): string"},{"location":"javascript/new-api_data-structures/","text":"Data Structures - JavaScript API # Introduction # JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11. Dictionary # Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object. set(key: string, value: any) # Adds or updates an item using the provided key. Numeric keys will be converted into strings. delete(key: string) # Removes an item from the collection. has(key: string): boolean # Returns true if the key is contained in the collection. get(key: string): any # Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence. forEach(callback: (value: any, key: string) => void) # Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key. size: number # This read-only property counts the number of items in the collection. List # Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array. add(value: any) # Adds a value to the list. If the value is already part of the list, this method will silently abort. clear() # Resets the collection. delete(value: any): boolean # Attempts to remove a value from the list, it returns true if the value has been part of the list. forEach(callback: (value: any) => void) # Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value. has(value: any): boolean # Returns true if the provided value is part of this list. size: number # This read-only property counts the number of items in the list. ObjectMap # This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects. set(key: Object, value: Object) # Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten. delete(key: Object) # Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection. has(key: Object): boolean # Returns true if there is a value for the provided key in this collection. get(key: Object): Object | undefined # Retrieves the value of the provided key, or undefined if the key was not found.","title":"Data Structures"},{"location":"javascript/new-api_data-structures/#data-structures-javascript-api","text":"","title":"Data Structures - JavaScript API"},{"location":"javascript/new-api_data-structures/#introduction","text":"JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11.","title":"Introduction"},{"location":"javascript/new-api_data-structures/#dictionary","text":"Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object.","title":"Dictionary"},{"location":"javascript/new-api_data-structures/#setkey-string-value-any","text":"Adds or updates an item using the provided key. Numeric keys will be converted into strings.","title":"set(key: string, value: any)"},{"location":"javascript/new-api_data-structures/#deletekey-string","text":"Removes an item from the collection.","title":"delete(key: string)"},{"location":"javascript/new-api_data-structures/#haskey-string-boolean","text":"Returns true if the key is contained in the collection.","title":"has(key: string): boolean"},{"location":"javascript/new-api_data-structures/#getkey-string-any","text":"Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence.","title":"get(key: string): any"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-key-string-void","text":"Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key.","title":"forEach(callback: (value: any, key: string) =&gt; void)"},{"location":"javascript/new-api_data-structures/#size-number","text":"This read-only property counts the number of items in the collection.","title":"size: number"},{"location":"javascript/new-api_data-structures/#list","text":"Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array.","title":"List"},{"location":"javascript/new-api_data-structures/#addvalue-any","text":"Adds a value to the list. If the value is already part of the list, this method will silently abort.","title":"add(value: any)"},{"location":"javascript/new-api_data-structures/#clear","text":"Resets the collection.","title":"clear()"},{"location":"javascript/new-api_data-structures/#deletevalue-any-boolean","text":"Attempts to remove a value from the list, it returns true if the value has been part of the list.","title":"delete(value: any): boolean"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-void","text":"Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value.","title":"forEach(callback: (value: any) =&gt; void)"},{"location":"javascript/new-api_data-structures/#hasvalue-any-boolean","text":"Returns true if the provided value is part of this list.","title":"has(value: any): boolean"},{"location":"javascript/new-api_data-structures/#size-number_1","text":"This read-only property counts the number of items in the list.","title":"size: number"},{"location":"javascript/new-api_data-structures/#objectmap","text":"This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects.","title":"ObjectMap"},{"location":"javascript/new-api_data-structures/#setkey-object-value-object","text":"Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten.","title":"set(key: Object, value: Object)"},{"location":"javascript/new-api_data-structures/#deletekey-object","text":"Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection.","title":"delete(key: Object)"},{"location":"javascript/new-api_data-structures/#haskey-object-boolean","text":"Returns true if there is a value for the provided key in this collection.","title":"has(key: Object): boolean"},{"location":"javascript/new-api_data-structures/#getkey-object-object-undefined","text":"Retrieves the value of the provided key, or undefined if the key was not found.","title":"get(key: Object): Object | undefined"},{"location":"javascript/new-api_dialogs/","text":"Dialogs - JavaScript API # Introduction # Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs. _dialogSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; }); id: string # The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id. source: any # There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog. options: Object # All configuration options and callbacks are handled through this object. options.backdropCloseOnClick: boolean # Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false . options.closable: boolean # Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only. options.closeButtonLabel: string # Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button. options.closeConfirmMessage: string # Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user. options.title: string # Defaults to \"\" . The phrase that is displayed in the dialog title. options.onBeforeClose: (id: string) => void # Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented. options.onClose: (id: string) => void # Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point. options.onShow: (content: Element) => void # Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet. setTitle(id: string | Object, title: string) # Sets the title of a dialog. setCallback(id: string | Object, key: string, value: (data: any) => void | null) # Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow . rebuild(id: string | Object) # Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback. close(id: string | Object) # Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation. getDialog(id: string | Object): Object # This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element. isOpen(id: string | Object): boolean # Returns true if the dialog exists and is open.","title":"Dialogs"},{"location":"javascript/new-api_dialogs/#dialogs-javascript-api","text":"","title":"Dialogs - JavaScript API"},{"location":"javascript/new-api_dialogs/#introduction","text":"Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs.","title":"Introduction"},{"location":"javascript/new-api_dialogs/#_dialogsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; });","title":"_dialogSetup()"},{"location":"javascript/new-api_dialogs/#id-string","text":"The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id.","title":"id: string"},{"location":"javascript/new-api_dialogs/#source-any","text":"There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog.","title":"source: any"},{"location":"javascript/new-api_dialogs/#options-object","text":"All configuration options and callbacks are handled through this object.","title":"options: Object"},{"location":"javascript/new-api_dialogs/#optionsbackdropcloseonclick-boolean","text":"Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false .","title":"options.backdropCloseOnClick: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosable-boolean","text":"Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only.","title":"options.closable: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosebuttonlabel-string","text":"Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button.","title":"options.closeButtonLabel: string"},{"location":"javascript/new-api_dialogs/#optionscloseconfirmmessage-string","text":"Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user.","title":"options.closeConfirmMessage: string"},{"location":"javascript/new-api_dialogs/#optionstitle-string","text":"Defaults to \"\" . The phrase that is displayed in the dialog title.","title":"options.title: string"},{"location":"javascript/new-api_dialogs/#optionsonbeforeclose-id-string-void","text":"Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented.","title":"options.onBeforeClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonclose-id-string-void","text":"Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point.","title":"options.onClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonshow-content-element-void","text":"Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet.","title":"options.onShow: (content: Element) =&gt; void"},{"location":"javascript/new-api_dialogs/#settitleid-string-object-title-string","text":"Sets the title of a dialog.","title":"setTitle(id: string | Object, title: string)"},{"location":"javascript/new-api_dialogs/#setcallbackid-string-object-key-string-value-data-any-void-null","text":"Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow .","title":"setCallback(id: string | Object, key: string, value: (data: any) =&gt; void | null)"},{"location":"javascript/new-api_dialogs/#rebuildid-string-object","text":"Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback.","title":"rebuild(id: string | Object)"},{"location":"javascript/new-api_dialogs/#closeid-string-object","text":"Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation.","title":"close(id: string | Object)"},{"location":"javascript/new-api_dialogs/#getdialogid-string-object-object","text":"This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element.","title":"getDialog(id: string | Object): Object"},{"location":"javascript/new-api_dialogs/#isopenid-string-object-boolean","text":"Returns true if the dialog exists and is open.","title":"isOpen(id: string | Object): boolean"},{"location":"javascript/new-api_dom/","text":"Working with the DOM - JavaScript API # Helper Functions # There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports. Dom/Util # createFragmentFromHtml(html: string): DocumentFragment # Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes. identify(element: Element): string # Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead. outerHeight(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom . outerWidth(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right . outerDimensions(element: Element): { height: number, width: number } # Computes the outer dimensions of an element including its margins. offset(element: Element): { top: number, left: number } # Computes the element's offset relative to the top left corner of the document. setInnerHtml(element: Element, innerHtml: string) # Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance. contains(element: Element, child: Element): boolean # Evaluates if element is a direct or indirect parent element of child . unwrapChildNodes(element: Element) # Moves all child nodes out of element while maintaining their order, then removes element from the document. Dom/ChangeListener # This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"DOM"},{"location":"javascript/new-api_dom/#working-with-the-dom-javascript-api","text":"","title":"Working with the DOM - JavaScript API"},{"location":"javascript/new-api_dom/#helper-functions","text":"There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports.","title":"Helper Functions"},{"location":"javascript/new-api_dom/#domutil","text":"","title":"Dom/Util"},{"location":"javascript/new-api_dom/#createfragmentfromhtmlhtml-string-documentfragment","text":"Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes.","title":"createFragmentFromHtml(html: string): DocumentFragment"},{"location":"javascript/new-api_dom/#identifyelement-element-string","text":"Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead.","title":"identify(element: Element): string"},{"location":"javascript/new-api_dom/#outerheightelement-element-styles-cssstyledeclaration-number","text":"Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom .","title":"outerHeight(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerwidthelement-element-styles-cssstyledeclaration-number","text":"Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right .","title":"outerWidth(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerdimensionselement-element-height-number-width-number","text":"Computes the outer dimensions of an element including its margins.","title":"outerDimensions(element: Element): { height: number, width: number }"},{"location":"javascript/new-api_dom/#offsetelement-element-top-number-left-number","text":"Computes the element's offset relative to the top left corner of the document.","title":"offset(element: Element): { top: number, left: number }"},{"location":"javascript/new-api_dom/#setinnerhtmlelement-element-innerhtml-string","text":"Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance.","title":"setInnerHtml(element: Element, innerHtml: string)"},{"location":"javascript/new-api_dom/#containselement-element-child-element-boolean","text":"Evaluates if element is a direct or indirect parent element of child .","title":"contains(element: Element, child: Element): boolean"},{"location":"javascript/new-api_dom/#unwrapchildnodeselement-element","text":"Moves all child nodes out of element while maintaining their order, then removes element from the document.","title":"unwrapChildNodes(element: Element)"},{"location":"javascript/new-api_dom/#domchangelistener","text":"This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"Dom/ChangeListener"},{"location":"javascript/new-api_events/","text":"Event Handling - JavaScript API # EventKey # This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); }); ArrowDown(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2193 key. ArrowLeft(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2190 key. ArrowRight(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2192 key. ArrowUp(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2191 key. Comma(event: KeyboardEvent): boolean # Returns true if the user has pressed the , key. Enter(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b2 key. Escape(event: KeyboardEvent): boolean # Returns true if the user has pressed the Esc key. Tab(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b9 key. EventHandler # A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time. Identifiying Events with the Developer Tools # The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo add(identifier: string, action: string, callback: (data: Object) => void): string # Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method. fire(identifier: string, action: string, data?: Object) # Triggers an event using an optional data object that is passed to each listener by reference. remove(identifier: string, action: string, uuid: string) # Removes a previously registered event listener using the UUID returned by add() . removeAll(identifier: string, action: string) # Removes all event listeners registered for the provided identifier and action . removeAllBySuffix(identifier: string, suffix: string) # Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"Event Handling"},{"location":"javascript/new-api_events/#event-handling-javascript-api","text":"","title":"Event Handling - JavaScript API"},{"location":"javascript/new-api_events/#eventkey","text":"This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); });","title":"EventKey"},{"location":"javascript/new-api_events/#arrowdownevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2193 key.","title":"ArrowDown(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowleftevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2190 key.","title":"ArrowLeft(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowrightevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2192 key.","title":"ArrowRight(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowupevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2191 key.","title":"ArrowUp(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#commaevent-keyboardevent-boolean","text":"Returns true if the user has pressed the , key.","title":"Comma(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#enterevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b2 key.","title":"Enter(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#escapeevent-keyboardevent-boolean","text":"Returns true if the user has pressed the Esc key.","title":"Escape(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#tabevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b9 key.","title":"Tab(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#eventhandler","text":"A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time.","title":"EventHandler"},{"location":"javascript/new-api_events/#identifiying-events-with-the-developer-tools","text":"The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo","title":"Identifiying Events with the Developer Tools"},{"location":"javascript/new-api_events/#addidentifier-string-action-string-callback-data-object-void-string","text":"Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method.","title":"add(identifier: string, action: string, callback: (data: Object) =&gt; void): string"},{"location":"javascript/new-api_events/#fireidentifier-string-action-string-data-object","text":"Triggers an event using an optional data object that is passed to each listener by reference.","title":"fire(identifier: string, action: string, data?: Object)"},{"location":"javascript/new-api_events/#removeidentifier-string-action-string-uuid-string","text":"Removes a previously registered event listener using the UUID returned by add() .","title":"remove(identifier: string, action: string, uuid: string)"},{"location":"javascript/new-api_events/#removeallidentifier-string-action-string","text":"Removes all event listeners registered for the provided identifier and action .","title":"removeAll(identifier: string, action: string)"},{"location":"javascript/new-api_events/#removeallbysuffixidentifier-string-suffix-string","text":"Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"removeAllBySuffix(identifier: string, suffix: string)"},{"location":"javascript/new-api_ui/","text":"User Interface - JavaScript API # Ui/Alignment # Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements. set(element: Element, referenceElement: Element, options: Object) # Calculates and sets the alignment of the element element . verticalOffset: number # Defaults to 0 . Creates a gap between the element and the reference element, in pixels. pointer: boolean # Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer . pointerOffset: number # Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center. pointerClassNames: string[] # Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element. refDimensionsElement: Element # Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions. horizontal: string # This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used. vertical: string # Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used. allowFlip: string # The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints. Ui/CloseOverlay # Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); }); add(identifier: string, callback: () => void) # Adds a callback that will be invoked when the user clicks anywhere else. Ui/Confirmation # Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); }); show(options: Object) # Displays a dialog overlay with actions buttons to confirm or reject the dialog. cancel: (parameters: Object) => void # Defaults to null . Callback that is invoked when the dialog was rejected. confirm: (parameters: Object) => void # Defaults to null . Callback that is invoked when the user has confirmed the dialog. message: string # Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled. messageIsHtml # Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML. parameters: Object # Optional list of parameter options that will be passed to the cancel() and confirm() callbacks. template: string # An optional HTML template that will be inserted into the dialog content area, but after the message section. Ui/Notification # Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); }); show(message: string, callback?: () => void, cssClassName?: string) # Shows the notification and executes the callback after 2 seconds.","title":"User Interface"},{"location":"javascript/new-api_ui/#user-interface-javascript-api","text":"","title":"User Interface - JavaScript API"},{"location":"javascript/new-api_ui/#uialignment","text":"Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements.","title":"Ui/Alignment"},{"location":"javascript/new-api_ui/#setelement-element-referenceelement-element-options-object","text":"Calculates and sets the alignment of the element element .","title":"set(element: Element, referenceElement: Element, options: Object)"},{"location":"javascript/new-api_ui/#verticaloffset-number","text":"Defaults to 0 . Creates a gap between the element and the reference element, in pixels.","title":"verticalOffset: number"},{"location":"javascript/new-api_ui/#pointer-boolean","text":"Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer .","title":"pointer: boolean"},{"location":"javascript/new-api_ui/#pointeroffset-number","text":"Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center.","title":"pointerOffset: number"},{"location":"javascript/new-api_ui/#pointerclassnames-string","text":"Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element.","title":"pointerClassNames: string[]"},{"location":"javascript/new-api_ui/#refdimensionselement-element","text":"Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions.","title":"refDimensionsElement: Element"},{"location":"javascript/new-api_ui/#horizontal-string","text":"This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used.","title":"horizontal: string"},{"location":"javascript/new-api_ui/#vertical-string","text":"Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used.","title":"vertical: string"},{"location":"javascript/new-api_ui/#allowflip-string","text":"The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints.","title":"allowFlip: string"},{"location":"javascript/new-api_ui/#uicloseoverlay","text":"Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); });","title":"Ui/CloseOverlay"},{"location":"javascript/new-api_ui/#addidentifier-string-callback-void","text":"Adds a callback that will be invoked when the user clicks anywhere else.","title":"add(identifier: string, callback: () =&gt; void)"},{"location":"javascript/new-api_ui/#uiconfirmation","text":"Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); });","title":"Ui/Confirmation"},{"location":"javascript/new-api_ui/#showoptions-object","text":"Displays a dialog overlay with actions buttons to confirm or reject the dialog.","title":"show(options: Object)"},{"location":"javascript/new-api_ui/#cancel-parameters-object-void","text":"Defaults to null . Callback that is invoked when the dialog was rejected.","title":"cancel: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#confirm-parameters-object-void","text":"Defaults to null . Callback that is invoked when the user has confirmed the dialog.","title":"confirm: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#message-string","text":"Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled.","title":"message: string"},{"location":"javascript/new-api_ui/#messageishtml","text":"Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML.","title":"messageIsHtml"},{"location":"javascript/new-api_ui/#parameters-object","text":"Optional list of parameter options that will be passed to the cancel() and confirm() callbacks.","title":"parameters: Object"},{"location":"javascript/new-api_ui/#template-string","text":"An optional HTML template that will be inserted into the dialog content area, but after the message section.","title":"template: string"},{"location":"javascript/new-api_ui/#uinotification","text":"Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); });","title":"Ui/Notification"},{"location":"javascript/new-api_ui/#showmessage-string-callback-void-cssclassname-string","text":"Shows the notification and executes the callback after 2 seconds.","title":"show(message: string, callback?: () =&gt; void, cssClassName?: string)"},{"location":"javascript/new-api_writing-a-module/","text":"Writing a Module - JavaScript API # Introduction # The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start. Defining a Module # The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; }); Loading a Module # Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script > Module Aliases # Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Writing a module"},{"location":"javascript/new-api_writing-a-module/#writing-a-module-javascript-api","text":"","title":"Writing a Module - JavaScript API"},{"location":"javascript/new-api_writing-a-module/#introduction","text":"The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start.","title":"Introduction"},{"location":"javascript/new-api_writing-a-module/#defining-a-module","text":"The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; });","title":"Defining a Module"},{"location":"javascript/new-api_writing-a-module/#loading-a-module","text":"Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script >","title":"Loading a Module"},{"location":"javascript/new-api_writing-a-module/#module-aliases","text":"Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Module Aliases"},{"location":"migration/wcf21/css/","text":"WCF 2.1.x - CSS # The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"CSS"},{"location":"migration/wcf21/css/#wcf-21x-css","text":"The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"WCF 2.1.x - CSS"},{"location":"migration/wcf21/package/","text":"WCF 2.1.x - Package Components # package.xml # Short Instructions # Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability. Example # <instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions> Exceptions # These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar acpMenu.xml # Renamed Categories # The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application Submenu Items # Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item. Example # <!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem> Common Icon Names # You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload box.xml # The box PIP has been added. cronjob.xml # Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete. eventListener.xml # Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete. menu.xml # The menu PIP has been added. menuItem.xml # The menuItem PIP has been added. objectType.xml # The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported. option.xml # The module.display category has been renamed into module.customization . page.xml # The page PIP has been added. pageMenu.xml # The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"Package Components"},{"location":"migration/wcf21/package/#wcf-21x-package-components","text":"","title":"WCF 2.1.x - Package Components"},{"location":"migration/wcf21/package/#packagexml","text":"","title":"package.xml"},{"location":"migration/wcf21/package/#short-instructions","text":"Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability.","title":"Short Instructions"},{"location":"migration/wcf21/package/#example","text":"<instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions>","title":"Example"},{"location":"migration/wcf21/package/#exceptions","text":"These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar","title":"Exceptions"},{"location":"migration/wcf21/package/#acpmenuxml","text":"","title":"acpMenu.xml"},{"location":"migration/wcf21/package/#renamed-categories","text":"The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application","title":"Renamed Categories"},{"location":"migration/wcf21/package/#submenu-items","text":"Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item.","title":"Submenu Items"},{"location":"migration/wcf21/package/#example_1","text":"<!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem>","title":"Example"},{"location":"migration/wcf21/package/#common-icon-names","text":"You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload","title":"Common Icon Names"},{"location":"migration/wcf21/package/#boxxml","text":"The box PIP has been added.","title":"box.xml"},{"location":"migration/wcf21/package/#cronjobxml","text":"Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete.","title":"cronjob.xml"},{"location":"migration/wcf21/package/#eventlistenerxml","text":"Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete.","title":"eventListener.xml"},{"location":"migration/wcf21/package/#menuxml","text":"The menu PIP has been added.","title":"menu.xml"},{"location":"migration/wcf21/package/#menuitemxml","text":"The menuItem PIP has been added.","title":"menuItem.xml"},{"location":"migration/wcf21/package/#objecttypexml","text":"The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported.","title":"objectType.xml"},{"location":"migration/wcf21/package/#optionxml","text":"The module.display category has been renamed into module.customization .","title":"option.xml"},{"location":"migration/wcf21/package/#pagexml","text":"The page PIP has been added.","title":"page.xml"},{"location":"migration/wcf21/package/#pagemenuxml","text":"The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"pageMenu.xml"},{"location":"migration/wcf21/php/","text":"WCF 2.1.x - PHP # Message Processing # WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done. Input Processing for Storage # The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message. Embedded Objects # Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); } Rendering the Message # The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml (); Simplified Output # At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 ); Plaintext Output # The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 ); Rebuilding Data # Converting from BBCode # Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml (); Extracting Embedded Objects # The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here Breadcrumbs / Page Location # Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object ); Pages and Forms # The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work. Search # ISearchableObjectType # Added the setLocation() method that is used to set the current page location based on the search result. SearchIndexManager # The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"PHP API"},{"location":"migration/wcf21/php/#wcf-21x-php","text":"","title":"WCF 2.1.x - PHP"},{"location":"migration/wcf21/php/#message-processing","text":"WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done.","title":"Message Processing"},{"location":"migration/wcf21/php/#input-processing-for-storage","text":"The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message.","title":"Input Processing for Storage"},{"location":"migration/wcf21/php/#embedded-objects","text":"Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); }","title":"Embedded Objects"},{"location":"migration/wcf21/php/#rendering-the-message","text":"The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml ();","title":"Rendering the Message"},{"location":"migration/wcf21/php/#simplified-output","text":"At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 );","title":"Simplified Output"},{"location":"migration/wcf21/php/#plaintext-output","text":"The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 );","title":"Plaintext Output"},{"location":"migration/wcf21/php/#rebuilding-data","text":"","title":"Rebuilding Data"},{"location":"migration/wcf21/php/#converting-from-bbcode","text":"Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml ();","title":"Converting from BBCode"},{"location":"migration/wcf21/php/#extracting-embedded-objects","text":"The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here","title":"Extracting Embedded Objects"},{"location":"migration/wcf21/php/#breadcrumbs-page-location","text":"Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object );","title":"Breadcrumbs / Page Location"},{"location":"migration/wcf21/php/#pages-and-forms","text":"The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work.","title":"Pages and Forms"},{"location":"migration/wcf21/php/#search","text":"","title":"Search"},{"location":"migration/wcf21/php/#isearchableobjecttype","text":"Added the setLocation() method that is used to set the current page location based on the search result.","title":"ISearchableObjectType"},{"location":"migration/wcf21/php/#searchindexmanager","text":"The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"SearchIndexManager"},{"location":"migration/wcf21/templates/","text":"WCF 2.1.x - Templates # Page Layout # The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information. Sidebars # Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section > Forms # The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing. Icons # The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" > Changed Icon Names # Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4. Changed Classes # .dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList Removed Elements and Classes # <nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible. Simple Example # The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' } Full Example # { * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' } Content Header # There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible. Recommended Approach # { * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture } Alternative # { capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Templates"},{"location":"migration/wcf21/templates/#wcf-21x-templates","text":"","title":"WCF 2.1.x - Templates"},{"location":"migration/wcf21/templates/#page-layout","text":"The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information.","title":"Page Layout"},{"location":"migration/wcf21/templates/#sidebars","text":"Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section >","title":"Sidebars"},{"location":"migration/wcf21/templates/#forms","text":"The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing.","title":"Forms"},{"location":"migration/wcf21/templates/#icons","text":"The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" >","title":"Icons"},{"location":"migration/wcf21/templates/#changed-icon-names","text":"Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4.","title":"Changed Icon Names"},{"location":"migration/wcf21/templates/#changed-classes","text":".dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList","title":"Changed Classes"},{"location":"migration/wcf21/templates/#removed-elements-and-classes","text":"<nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible.","title":"Removed Elements and Classes"},{"location":"migration/wcf21/templates/#simple-example","text":"The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' }","title":"Simple Example"},{"location":"migration/wcf21/templates/#full-example","text":"{ * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' }","title":"Full Example"},{"location":"migration/wcf21/templates/#content-header","text":"There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible.","title":"Content Header"},{"location":"migration/wcf21/templates/#recommended-approach","text":"{ * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture }","title":"Recommended Approach"},{"location":"migration/wcf21/templates/#alternative","text":"{ capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Alternative"},{"location":"migration/wsc30/css/","text":"Migrating from WSC 3.0 - CSS # New Style Variables # The new style variables are only applied to styles that have the compatibility set to WSC 3.1 wcfContentContainer # The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color wcfEditorButton # These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons Color Variables in alert.scss # The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"CSS"},{"location":"migration/wsc30/css/#migrating-from-wsc-30-css","text":"","title":"Migrating from WSC 3.0 - CSS"},{"location":"migration/wsc30/css/#new-style-variables","text":"The new style variables are only applied to styles that have the compatibility set to WSC 3.1","title":"New Style Variables"},{"location":"migration/wsc30/css/#wcfcontentcontainer","text":"The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color","title":"wcfContentContainer"},{"location":"migration/wsc30/css/#wcfeditorbutton","text":"These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons","title":"wcfEditorButton"},{"location":"migration/wsc30/css/#color-variables-in-alertscss","text":"The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"Color Variables in alert.scss"},{"location":"migration/wsc30/javascript/","text":"Migrating from WSC 3.0 - JavaScript # Accelerated Guest View / Tiny Builds # The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever. Code Templates for Tiny Builds # The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now. Legacy JavaScript # if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); } require.js Modules # define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; }); Including tinified builds through {js} # The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state. Real Error Messages for AJAX Responses # The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms. Example Code # define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; }); Simplified Form Submit in Dialogs # Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field. Helper Function for Inline Error Messages # Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef) Example Code # require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"JavaScript API"},{"location":"migration/wsc30/javascript/#migrating-from-wsc-30-javascript","text":"","title":"Migrating from WSC 3.0 - JavaScript"},{"location":"migration/wsc30/javascript/#accelerated-guest-view-tiny-builds","text":"The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever.","title":"Accelerated Guest View / Tiny Builds"},{"location":"migration/wsc30/javascript/#code-templates-for-tiny-builds","text":"The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now.","title":"Code Templates for Tiny Builds"},{"location":"migration/wsc30/javascript/#legacy-javascript","text":"if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); }","title":"Legacy JavaScript"},{"location":"migration/wsc30/javascript/#requirejs-modules","text":"define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; });","title":"require.js Modules"},{"location":"migration/wsc30/javascript/#including-tinified-builds-through-js","text":"The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state.","title":"Including tinified builds through {js}"},{"location":"migration/wsc30/javascript/#real-error-messages-for-ajax-responses","text":"The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms.","title":"Real Error Messages for AJAX Responses"},{"location":"migration/wsc30/javascript/#example-code","text":"define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; });","title":"Example Code"},{"location":"migration/wsc30/javascript/#simplified-form-submit-in-dialogs","text":"Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field.","title":"Simplified Form Submit in Dialogs"},{"location":"migration/wsc30/javascript/#helper-function-for-inline-error-messages","text":"Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef)","title":"Helper Function for Inline Error Messages"},{"location":"migration/wsc30/javascript/#example-code_1","text":"require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"Example Code"},{"location":"migration/wsc30/package/","text":"Migrating from WSC 3.0 - Package Components # Cronjob Scheduler uses Server Timezone # The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel. Exclude Pages from becoming a Landing Page # Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml . Example Code # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data> New Package Installation Plugin for Media Providers # Please refer to the documentation of the mediaProvider.xml to learn more. Limited Forward-Compatibility for Plugins # Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Package Components"},{"location":"migration/wsc30/package/#migrating-from-wsc-30-package-components","text":"","title":"Migrating from WSC 3.0 - Package Components"},{"location":"migration/wsc30/package/#cronjob-scheduler-uses-server-timezone","text":"The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel.","title":"Cronjob Scheduler uses Server Timezone"},{"location":"migration/wsc30/package/#exclude-pages-from-becoming-a-landing-page","text":"Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml .","title":"Exclude Pages from becoming a Landing Page"},{"location":"migration/wsc30/package/#example-code","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data>","title":"Example Code"},{"location":"migration/wsc30/package/#new-package-installation-plugin-for-media-providers","text":"Please refer to the documentation of the mediaProvider.xml to learn more.","title":"New Package Installation Plugin for Media Providers"},{"location":"migration/wsc30/package/#limited-forward-compatibility-for-plugins","text":"Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Limited Forward-Compatibility for Plugins"},{"location":"migration/wsc30/php/","text":"Migrating from WSC 3.0 - PHP # Approval-System for Comments # Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; } Raw HTML in User Activity Events # User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; } Permission to View Likes of an Object # Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } } Developer Tools: Sync Feature # The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } } Media Providers # Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation. Example Implementation # mediaProvider.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data> PHP Callback # The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } } Re-Evaluate HTML Messages # You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously. Example Usage # <? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"PHP API"},{"location":"migration/wsc30/php/#migrating-from-wsc-30-php","text":"","title":"Migrating from WSC 3.0 - PHP"},{"location":"migration/wsc30/php/#approval-system-for-comments","text":"Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; }","title":"Approval-System for Comments"},{"location":"migration/wsc30/php/#raw-html-in-user-activity-events","text":"User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; }","title":"Raw HTML in User Activity Events"},{"location":"migration/wsc30/php/#permission-to-view-likes-of-an-object","text":"Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } }","title":"Permission to View Likes of an Object"},{"location":"migration/wsc30/php/#developer-tools-sync-feature","text":"The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } }","title":"Developer Tools: Sync Feature"},{"location":"migration/wsc30/php/#media-providers","text":"Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation.","title":"Media Providers"},{"location":"migration/wsc30/php/#example-implementation","text":"","title":"Example Implementation"},{"location":"migration/wsc30/php/#mediaproviderxml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data>","title":"mediaProvider.xml"},{"location":"migration/wsc30/php/#php-callback","text":"The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } }","title":"PHP Callback"},{"location":"migration/wsc30/php/#re-evaluate-html-messages","text":"You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously.","title":"Re-Evaluate HTML Messages"},{"location":"migration/wsc30/php/#example-usage","text":"<? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"Example Usage"},{"location":"migration/wsc30/templates/","text":"Migrating from WSC 3.0 - Templates # Comment-System Overhaul # Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments. Adding Comments # Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul > Redesigned ACP User List # Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li > Sidebar Toogle-Buttons on Mobile Device # You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Templates"},{"location":"migration/wsc30/templates/#migrating-from-wsc-30-templates","text":"","title":"Migrating from WSC 3.0 - Templates"},{"location":"migration/wsc30/templates/#comment-system-overhaul","text":"Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments.","title":"Comment-System Overhaul"},{"location":"migration/wsc30/templates/#adding-comments","text":"Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul >","title":"Adding Comments"},{"location":"migration/wsc30/templates/#redesigned-acp-user-list","text":"Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li >","title":"Redesigned ACP User List"},{"location":"migration/wsc30/templates/#sidebar-toogle-buttons-on-mobile-device","text":"You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Sidebar Toogle-Buttons on Mobile Device"},{"location":"migration/wsc31/form-builder/","text":"Migrating from WSC 3.1 - Form Builder # Example: Two Text Form Fields # As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#migrating-from-wsc-31-form-builder","text":"","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#example-two-text-form-fields","text":"As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Example: Two Text Form Fields"},{"location":"migration/wsc31/like/","text":"Migrating from WSC 3.1 - Like System # Introduction # With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments. Limitations if no adjustments are made to the existing code # If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes Migration # Notifications # Mark notification as compatible # Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again. Language Variables # Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # {prefix}.like.title Reaction to a {objectName} {prefix}.like.title.stacked {#$count} users reacted to your {objectName} {prefix}.like.message {@$author->getAnchorTag()} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} others{/if} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{objectTypeName}.like.notification.like Notify me when someone reacted to my {objectName} German # {prefix}.like.title Reaktion auf einen {objectName} {prefix}.like.title.stacked {#$count} Benutzern haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert {prefix}.like.message {@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{object_type_name}.like.notification.like Jemandem hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert Recent Activity # To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}. German # wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert. Comments # If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type> Forward Compatibility # So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#migrating-from-wsc-31-like-system","text":"","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#introduction","text":"With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments.","title":"Introduction"},{"location":"migration/wsc31/like/#limitations-if-no-adjustments-are-made-to-the-existing-code","text":"If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes","title":"Limitations if no adjustments are made to the existing code"},{"location":"migration/wsc31/like/#migration","text":"","title":"Migration"},{"location":"migration/wsc31/like/#notifications","text":"","title":"Notifications"},{"location":"migration/wsc31/like/#mark-notification-as-compatible","text":"Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again.","title":"Mark notification as compatible"},{"location":"migration/wsc31/like/#language-variables","text":"Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Language Variables"},{"location":"migration/wsc31/like/#recent-activity","text":"To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Recent Activity"},{"location":"migration/wsc31/like/#english_1","text":"wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}.","title":"English"},{"location":"migration/wsc31/like/#german_1","text":"wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert.","title":"German"},{"location":"migration/wsc31/like/#comments","text":"If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type>","title":"Comments"},{"location":"migration/wsc31/like/#forward-compatibility","text":"So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Forward Compatibility"},{"location":"migration/wsc31/php/","text":"Migrating from WSC 3.1 - PHP # Form Builder # WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway. Like System # WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here . User Content Providers # User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects. PHP Class # First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } } Object Type # Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type: nicevalue # Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first. hidden # Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically! requiredobjecttype # Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important. PHP Database API # WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP API"},{"location":"migration/wsc31/php/#migrating-from-wsc-31-php","text":"","title":"Migrating from WSC 3.1 - PHP"},{"location":"migration/wsc31/php/#form-builder","text":"WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway.","title":"Form Builder"},{"location":"migration/wsc31/php/#like-system","text":"WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here .","title":"Like System"},{"location":"migration/wsc31/php/#user-content-providers","text":"User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects.","title":"User Content Providers"},{"location":"migration/wsc31/php/#php-class","text":"First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } }","title":"PHP Class"},{"location":"migration/wsc31/php/#object-type","text":"Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type:","title":"Object Type"},{"location":"migration/wsc31/php/#nicevalue","text":"Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first.","title":"nicevalue"},{"location":"migration/wsc31/php/#hidden","text":"Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically!","title":"hidden"},{"location":"migration/wsc31/php/#requiredobjecttype","text":"Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important.","title":"requiredobjecttype"},{"location":"migration/wsc31/php/#php-database-api","text":"WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP Database API"},{"location":"migration/wsc52/libraries/","text":"Migrating from WSC 5.2 - Third Party Libraries # SCSS Compiler # WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 . Guzzle # WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Third Party Libraries"},{"location":"migration/wsc52/libraries/#migrating-from-wsc-52-third-party-libraries","text":"","title":"Migrating from WSC 5.2 - Third Party Libraries"},{"location":"migration/wsc52/libraries/#scss-compiler","text":"WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 .","title":"SCSS Compiler"},{"location":"migration/wsc52/libraries/#guzzle","text":"WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Guzzle"},{"location":"migration/wsc52/php/","text":"Migrating from WSC 5.2 - PHP # Comments # The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager . Event Listeners # The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener . Email Activation # Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() . *AddForm # WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]); User Generated Links # It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available. Resource Management When Scaling Images # It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details. Toggle for Accelerated Mobile Pages (AMP) # Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"PHP API"},{"location":"migration/wsc52/php/#migrating-from-wsc-52-php","text":"","title":"Migrating from WSC 5.2 - PHP"},{"location":"migration/wsc52/php/#comments","text":"The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager .","title":"Comments"},{"location":"migration/wsc52/php/#event-listeners","text":"The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener .","title":"Event Listeners"},{"location":"migration/wsc52/php/#email-activation","text":"Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() .","title":"Email Activation"},{"location":"migration/wsc52/php/#addform","text":"WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]);","title":"*AddForm"},{"location":"migration/wsc52/php/#user-generated-links","text":"It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available.","title":"User Generated Links"},{"location":"migration/wsc52/php/#resource-management-when-scaling-images","text":"It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details.","title":"Resource Management When Scaling Images"},{"location":"migration/wsc52/php/#toggle-for-accelerated-mobile-pages-amp","text":"Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"Toggle for Accelerated Mobile Pages (AMP)"},{"location":"migration/wsc52/templates/","text":"Migrating from WSC 5.2 - Templates and Languages # {jslang} # Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script> Template Plugins # The {anchor} , {plural} , and {user} template plugins have been added. Notification Language Items # In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language } Popovers # Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Templates and Languages"},{"location":"migration/wsc52/templates/#migrating-from-wsc-52-templates-and-languages","text":"","title":"Migrating from WSC 5.2 - Templates and Languages"},{"location":"migration/wsc52/templates/#jslang","text":"Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script>","title":"{jslang}"},{"location":"migration/wsc52/templates/#template-plugins","text":"The {anchor} , {plural} , and {user} template plugins have been added.","title":"Template Plugins"},{"location":"migration/wsc52/templates/#notification-language-items","text":"In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language }","title":"Notification Language Items"},{"location":"migration/wsc52/templates/#popovers","text":"Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Popovers"},{"location":"migration/wsc53/javascript/","text":"Migrating from WSC 5.3 - JavaScript # WCF_CLICK_EVENT # For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev )); WCF.Action.Delete and WCF.Action.Toggle # WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" { * * } data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody> { foreach from = $objects item = foo } <tr class=\"jsObjectActionObject\" data-object-id=\" { @ $foo -> getObjectID () } \"> <td class=\"columnIcon\"> { objectAction action = \"toggle\" isDisabled = $foo -> isDisabled } { objectAction action = \"delete\" objectTitle = $foo -> getTitle () } { * \u2026 * } </td> { * \u2026 * } </tr> { /foreach } </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 . WCF.Table.EmptyTableHandler # When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"JavaScript"},{"location":"migration/wsc53/javascript/#migrating-from-wsc-53-javascript","text":"","title":"Migrating from WSC 5.3 - JavaScript"},{"location":"migration/wsc53/javascript/#wcf_click_event","text":"For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev ));","title":"WCF_CLICK_EVENT"},{"location":"migration/wsc53/javascript/#wcfactiondelete-and-wcfactiontoggle","text":"WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" { * * } data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody> { foreach from = $objects item = foo } <tr class=\"jsObjectActionObject\" data-object-id=\" { @ $foo -> getObjectID () } \"> <td class=\"columnIcon\"> { objectAction action = \"toggle\" isDisabled = $foo -> isDisabled } { objectAction action = \"delete\" objectTitle = $foo -> getTitle () } { * \u2026 * } </td> { * \u2026 * } </tr> { /foreach } </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 .","title":"WCF.Action.Delete and WCF.Action.Toggle"},{"location":"migration/wsc53/javascript/#wcftableemptytablehandler","text":"When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"WCF.Table.EmptyTableHandler"},{"location":"migration/wsc53/libraries/","text":"Migrating from WSC 5.3 - Third Party Libraries # Guzzle # The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7. Emogrifier / CSS Inliner # The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases. scssphp # scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details. Constant Time Encoder # WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Third Party Libraries"},{"location":"migration/wsc53/libraries/#migrating-from-wsc-53-third-party-libraries","text":"","title":"Migrating from WSC 5.3 - Third Party Libraries"},{"location":"migration/wsc53/libraries/#guzzle","text":"The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7.","title":"Guzzle"},{"location":"migration/wsc53/libraries/#emogrifier-css-inliner","text":"The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases.","title":"Emogrifier / CSS Inliner"},{"location":"migration/wsc53/libraries/#scssphp","text":"scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details.","title":"scssphp"},{"location":"migration/wsc53/libraries/#constant-time-encoder","text":"WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Constant Time Encoder"},{"location":"migration/wsc53/php/","text":"Migrating from WSC 5.3 - PHP # Minimum requirements # The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details. Flood Control # To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class! DatabasePackageInstallationPlugin # DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file. PHP Database API # The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed. Captcha # The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details. Search # The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details. Styles # The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details. Tags # Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details. Avatars # The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"PHP API"},{"location":"migration/wsc53/php/#migrating-from-wsc-53-php","text":"","title":"Migrating from WSC 5.3 - PHP"},{"location":"migration/wsc53/php/#minimum-requirements","text":"The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details.","title":"Minimum requirements"},{"location":"migration/wsc53/php/#flood-control","text":"To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class!","title":"Flood Control"},{"location":"migration/wsc53/php/#databasepackageinstallationplugin","text":"DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file.","title":"DatabasePackageInstallationPlugin"},{"location":"migration/wsc53/php/#php-database-api","text":"The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed.","title":"PHP Database API"},{"location":"migration/wsc53/php/#captcha","text":"The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details.","title":"Captcha"},{"location":"migration/wsc53/php/#search","text":"The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details.","title":"Search"},{"location":"migration/wsc53/php/#styles","text":"The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details.","title":"Styles"},{"location":"migration/wsc53/php/#tags","text":"Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details.","title":"Tags"},{"location":"migration/wsc53/php/#avatars","text":"The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"Avatars"},{"location":"migration/wsc53/session/","text":"Migrating from WSC 5.3 - Session Handling and Authentication # WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data. Summary and Concepts # Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other. Legacy Persistent Login # The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight. Multiple Sessions # To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler . Merged ACP and Frontend Sessions # WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa. Improved Authentication and Reauthentication # WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence. Additions and Changes # Password Hashing # WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems. Session Storage # The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class! Reauthentication # For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 . Multi-factor Authentication # To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in. Adding Multi-factor Methods # Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions. Deprecations and Removals # SessionHandler # Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details. ACP Sessions # The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions. Cookies # The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed. Virtual Sessions # The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables. Security Token Constants # The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions. PasswordUtil and Double BCrypt Hashes # Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"Session Handling and Authentication"},{"location":"migration/wsc53/session/#migrating-from-wsc-53-session-handling-and-authentication","text":"WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data.","title":"Migrating from WSC 5.3 - Session Handling and Authentication"},{"location":"migration/wsc53/session/#summary-and-concepts","text":"Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other.","title":"Summary and Concepts"},{"location":"migration/wsc53/session/#legacy-persistent-login","text":"The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight.","title":"Legacy Persistent Login"},{"location":"migration/wsc53/session/#multiple-sessions","text":"To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler .","title":"Multiple Sessions"},{"location":"migration/wsc53/session/#merged-acp-and-frontend-sessions","text":"WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa.","title":"Merged ACP and Frontend Sessions"},{"location":"migration/wsc53/session/#improved-authentication-and-reauthentication","text":"WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence.","title":"Improved Authentication and Reauthentication"},{"location":"migration/wsc53/session/#additions-and-changes","text":"","title":"Additions and Changes"},{"location":"migration/wsc53/session/#password-hashing","text":"WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems.","title":"Password Hashing"},{"location":"migration/wsc53/session/#session-storage","text":"The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class!","title":"Session Storage"},{"location":"migration/wsc53/session/#reauthentication","text":"For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 .","title":"Reauthentication"},{"location":"migration/wsc53/session/#multi-factor-authentication","text":"To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in.","title":"Multi-factor Authentication"},{"location":"migration/wsc53/session/#adding-multi-factor-methods","text":"Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions.","title":"Adding Multi-factor Methods"},{"location":"migration/wsc53/session/#deprecations-and-removals","text":"","title":"Deprecations and Removals"},{"location":"migration/wsc53/session/#sessionhandler","text":"Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details.","title":"SessionHandler"},{"location":"migration/wsc53/session/#acp-sessions","text":"The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions.","title":"ACP Sessions"},{"location":"migration/wsc53/session/#cookies","text":"The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed.","title":"Cookies"},{"location":"migration/wsc53/session/#virtual-sessions","text":"The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables.","title":"Virtual Sessions"},{"location":"migration/wsc53/session/#security-token-constants","text":"The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions.","title":"Security Token Constants"},{"location":"migration/wsc53/session/#passwordutil-and-double-bcrypt-hashes","text":"Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"PasswordUtil and Double BCrypt Hashes"},{"location":"migration/wsc53/templates/","text":"Migrating from WSC 5.3 - Templates and Languages # {csrfToken} # Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"Templates"},{"location":"migration/wsc53/templates/#migrating-from-wsc-53-templates-and-languages","text":"","title":"Migrating from WSC 5.3 - Templates and Languages"},{"location":"migration/wsc53/templates/#csrftoken","text":"Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"{csrfToken}"},{"location":"package/database-php-api/","text":"Database PHP API # Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ]; Database Tables # There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop () Columns # To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull () Foreign Keys # Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data. Indices # Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Database PHP API"},{"location":"package/database-php-api/#database-php-api","text":"Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ];","title":"Database PHP API"},{"location":"package/database-php-api/#database-tables","text":"There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop ()","title":"Database Tables"},{"location":"package/database-php-api/#columns","text":"To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull ()","title":"Columns"},{"location":"package/database-php-api/#foreign-keys","text":"Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data.","title":"Foreign Keys"},{"location":"package/database-php-api/#indices","text":"Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Indices"},{"location":"package/package-xml/","text":"package.xml # The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package> Elements # <package> # The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ). <packageinformation> # Holds the entire meta data of the package. <packagename> # This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation> <packagedescription> # Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details. <version> # The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction) <date> # Must be a valid ISO 8601 date, e.g. 2013-12-27 . <authorinformation> # Holds meta data regarding the package's author. <author> # Can be anything you want. <authorurl> # (optional) URL to the author's website. <requiredpackages> # A list of packages including their version required for this package to work. <requiredpackage> # Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml . <optionalpackage> # A list of optional packages which can be selected by the user at the very end of the installation process. <optionalpackage> # Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml . <excludedpackages> # List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed. <excludedpackage> # Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher. <compatibility> # Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility> Existing API versions # WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a <instructions> # List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first. <instructions type=\"install\"> # List of instructions for a new installation of this package. <instructions type=\"update\" fromversion=\"\u2026\"> # The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 . <instruction> # Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" /> <void/> # Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"package.xml"},{"location":"package/package-xml/#packagexml","text":"The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version.","title":"package.xml"},{"location":"package/package-xml/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package>","title":"Example"},{"location":"package/package-xml/#elements","text":"","title":"Elements"},{"location":"package/package-xml/#package","text":"The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ).","title":"&lt;package&gt;"},{"location":"package/package-xml/#packageinformation","text":"Holds the entire meta data of the package.","title":"&lt;packageinformation&gt;"},{"location":"package/package-xml/#packagename","text":"This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation>","title":"&lt;packagename&gt;"},{"location":"package/package-xml/#packagedescription","text":"Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details.","title":"&lt;packagedescription&gt;"},{"location":"package/package-xml/#version","text":"The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction)","title":"&lt;version&gt;"},{"location":"package/package-xml/#date","text":"Must be a valid ISO 8601 date, e.g. 2013-12-27 .","title":"&lt;date&gt;"},{"location":"package/package-xml/#authorinformation","text":"Holds meta data regarding the package's author.","title":"&lt;authorinformation&gt;"},{"location":"package/package-xml/#author","text":"Can be anything you want.","title":"&lt;author&gt;"},{"location":"package/package-xml/#authorurl","text":"(optional) URL to the author's website.","title":"&lt;authorurl&gt;"},{"location":"package/package-xml/#requiredpackages","text":"A list of packages including their version required for this package to work.","title":"&lt;requiredpackages&gt;"},{"location":"package/package-xml/#requiredpackage","text":"Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml .","title":"&lt;requiredpackage&gt;"},{"location":"package/package-xml/#optionalpackage","text":"A list of optional packages which can be selected by the user at the very end of the installation process.","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#optionalpackage_1","text":"Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml .","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#excludedpackages","text":"List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed.","title":"&lt;excludedpackages&gt;"},{"location":"package/package-xml/#excludedpackage","text":"Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher.","title":"&lt;excludedpackage&gt;"},{"location":"package/package-xml/#compatibility","text":"Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility>","title":"&lt;compatibility&gt;"},{"location":"package/package-xml/#existing-api-versions","text":"WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a","title":"Existing API versions"},{"location":"package/package-xml/#instructions","text":"List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first.","title":"&lt;instructions&gt;"},{"location":"package/package-xml/#instructions-typeinstall","text":"List of instructions for a new installation of this package.","title":"&lt;instructions type=\"install\"&gt;"},{"location":"package/package-xml/#instructions-typeupdate-fromversion","text":"The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 .","title":"&lt;instructions type=\"update\" fromversion=\"\u2026\"&gt;"},{"location":"package/package-xml/#instruction","text":"Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" />","title":"&lt;instruction&gt;"},{"location":"package/package-xml/#void","text":"Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"&lt;void/&gt;"},{"location":"package/pip/","text":"Package Installation Plugins # Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary. Built-In PIPs # Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Overview"},{"location":"package/pip/#package-installation-plugins","text":"Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary.","title":"Package Installation Plugins"},{"location":"package/pip/#built-in-pips","text":"Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Built-In PIPs"},{"location":"package/pip/acl-option/","text":"ACL Option Package Installation Plugin # Add customizable permissions for individual objects. Option Components # Each acl option is described as an <option> element with the mandatory attribute name . <categoryname> # Optional The name of the acl option category to which the option belongs. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Category Components # Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"aclOption"},{"location":"package/pip/acl-option/#acl-option-package-installation-plugin","text":"Add customizable permissions for individual objects.","title":"ACL Option Package Installation Plugin"},{"location":"package/pip/acl-option/#option-components","text":"Each acl option is described as an <option> element with the mandatory attribute name .","title":"Option Components"},{"location":"package/pip/acl-option/#categoryname","text":"Optional The name of the acl option category to which the option belongs.","title":"&lt;categoryname&gt;"},{"location":"package/pip/acl-option/#objecttype","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#category-components","text":"Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value.","title":"Category Components"},{"location":"package/pip/acl-option/#objecttype_1","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"Example"},{"location":"package/pip/acp-menu/","text":"ACP Menu Package Installation Plugin # Registers new ACP menu items. Components # Each item is described as an <acpmenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <icon> # Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"acpMenu"},{"location":"package/pip/acp-menu/#acp-menu-package-installation-plugin","text":"Registers new ACP menu items.","title":"ACP Menu Package Installation Plugin"},{"location":"package/pip/acp-menu/#components","text":"Each item is described as an <acpmenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/acp-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/acp-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/acp-menu/#icon","text":"Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class.","title":"&lt;icon&gt;"},{"location":"package/pip/acp-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/acp-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/acp-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"Example"},{"location":"package/pip/acp-search-provider/","text":"ACP Search Provider Package Installation Plugin # Registers data provider for the admin panel search. Components # Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name . <classname> # The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface. <showorder> # Optional Determines at which position of the search result list the provided results are shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"acpSearchProvider"},{"location":"package/pip/acp-search-provider/#acp-search-provider-package-installation-plugin","text":"Registers data provider for the admin panel search.","title":"ACP Search Provider Package Installation Plugin"},{"location":"package/pip/acp-search-provider/#components","text":"Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-search-provider/#classname","text":"The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/acp-search-provider/#showorder","text":"Optional Determines at which position of the search result list the provided results are shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-search-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"Example"},{"location":"package/pip/acp-template/","text":"ACP Template Installation Plugin # Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages. Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file. Attributes # application # The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory. Example in package.xml # <instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"acpTemplate"},{"location":"package/pip/acp-template/#acp-template-installation-plugin","text":"Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages.","title":"ACP Template Installation Plugin"},{"location":"package/pip/acp-template/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/acp-template/#attributes","text":"","title":"Attributes"},{"location":"package/pip/acp-template/#application","text":"The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory.","title":"application"},{"location":"package/pip/acp-template/#example-in-packagexml","text":"<instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"Example in package.xml"},{"location":"package/pip/bbcode/","text":"BBCode Package Installation Plugin # Registers new BBCodes. Components # Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user. <htmlopen> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode. <htmlclose> # Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags. <classname> # The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered. <wysiwygicon> # Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar. <buttonlabel> # Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon. <sourcecode> # Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead. <isBlockElement> # Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification). <attributes> # Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer. <html> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value. <validationpattern> # Optional Defines a regular expression that is used to validate the value of the attribute. <required> # Optional Specifies whether this attribute must be provided. <usetext> # Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"bbcode"},{"location":"package/pip/bbcode/#bbcode-package-installation-plugin","text":"Registers new BBCodes.","title":"BBCode Package Installation Plugin"},{"location":"package/pip/bbcode/#components","text":"Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user.","title":"Components"},{"location":"package/pip/bbcode/#htmlopen","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode.","title":"&lt;htmlopen&gt;"},{"location":"package/pip/bbcode/#htmlclose","text":"Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags.","title":"&lt;htmlclose&gt;"},{"location":"package/pip/bbcode/#classname","text":"The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered.","title":"&lt;classname&gt;"},{"location":"package/pip/bbcode/#wysiwygicon","text":"Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar.","title":"&lt;wysiwygicon&gt;"},{"location":"package/pip/bbcode/#buttonlabel","text":"Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon.","title":"&lt;buttonlabel&gt;"},{"location":"package/pip/bbcode/#sourcecode","text":"Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead.","title":"&lt;sourcecode&gt;"},{"location":"package/pip/bbcode/#isblockelement","text":"Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification).","title":"&lt;isBlockElement&gt;"},{"location":"package/pip/bbcode/#attributes","text":"Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer.","title":"&lt;attributes&gt;"},{"location":"package/pip/bbcode/#html","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value.","title":"&lt;html&gt;"},{"location":"package/pip/bbcode/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of the attribute.","title":"&lt;validationpattern&gt;"},{"location":"package/pip/bbcode/#required","text":"Optional Specifies whether this attribute must be provided.","title":"&lt;required&gt;"},{"location":"package/pip/bbcode/#usetext","text":"Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value.","title":"&lt;usetext&gt;"},{"location":"package/pip/bbcode/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"Example"},{"location":"package/pip/box/","text":"Box Package Installation Plugin # Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based. Components # Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity . <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <boxType> # system # The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <objectType> # Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController . <position> # The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top Placeholder Positions # --8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\" <showHeader> # Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 . <visibleEverywhere> # Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> . <visibilityExceptions> # Inverts the <visibleEverywhere> setting for the listed pages only. <cssClassName> # Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the box title shown to the end users. <content> # The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"box"},{"location":"package/pip/box/#box-package-installation-plugin","text":"Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based.","title":"Box Package Installation Plugin"},{"location":"package/pip/box/#components","text":"Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity .","title":"Components"},{"location":"package/pip/box/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/box/#boxtype","text":"","title":"&lt;boxType&gt;"},{"location":"package/pip/box/#system","text":"The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element.","title":"system"},{"location":"package/pip/box/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/box/#objecttype","text":"Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController .","title":"&lt;objectType&gt;"},{"location":"package/pip/box/#position","text":"The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top","title":"&lt;position&gt;"},{"location":"package/pip/box/#placeholder-positions","text":"--8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\"","title":"Placeholder Positions"},{"location":"package/pip/box/#showheader","text":"Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 .","title":"&lt;showHeader&gt;"},{"location":"package/pip/box/#visibleeverywhere","text":"Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> .","title":"&lt;visibleEverywhere&gt;"},{"location":"package/pip/box/#visibilityexceptions","text":"Inverts the <visibleEverywhere> setting for the listed pages only.","title":"&lt;visibilityExceptions&gt;"},{"location":"package/pip/box/#cssclassname","text":"Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance.","title":"&lt;cssClassName&gt;"},{"location":"package/pip/box/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/box/#title","text":"The title element is required and controls the box title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/box/#content_1","text":"The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/box/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"Example"},{"location":"package/pip/clipboard-action/","text":"Clipboard Action Package Installation Plugin # Registers clipboard actions. Components # Each clipboard action is described as an <action> element with the mandatory attribute name . <actionclassname> # The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction . <pages> # Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available. <showorder> # Optional Determines at which position of the clipboard action list the action is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"clipboardAction"},{"location":"package/pip/clipboard-action/#clipboard-action-package-installation-plugin","text":"Registers clipboard actions.","title":"Clipboard Action Package Installation Plugin"},{"location":"package/pip/clipboard-action/#components","text":"Each clipboard action is described as an <action> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/clipboard-action/#actionclassname","text":"The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction .","title":"&lt;actionclassname&gt;"},{"location":"package/pip/clipboard-action/#pages","text":"Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available.","title":"&lt;pages&gt;"},{"location":"package/pip/clipboard-action/#showorder","text":"Optional Determines at which position of the clipboard action list the action is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/clipboard-action/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"Example"},{"location":"package/pip/core-object/","text":"Core Object Package Installation Plugin # Registers wcf\\system\\SingletonFactory objects to be accessible in templates. Components # Each item is described as a <coreobject> element with the mandatory element objectname . <objectname> # The fully qualified class name of the class. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"coreObject"},{"location":"package/pip/core-object/#core-object-package-installation-plugin","text":"Registers wcf\\system\\SingletonFactory objects to be accessible in templates.","title":"Core Object Package Installation Plugin"},{"location":"package/pip/core-object/#components","text":"Each item is described as a <coreobject> element with the mandatory element objectname .","title":"Components"},{"location":"package/pip/core-object/#objectname","text":"The fully qualified class name of the class.","title":"&lt;objectname&gt;"},{"location":"package/pip/core-object/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"Example"},{"location":"package/pip/cronjob/","text":"Cronjob Package Installation Plugin # Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past. Components # Each cronjob is described as an <cronjob> element with the mandatory attribute name . <classname> # The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface. <description> # The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator. <start*> # All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax. <canBeEdited> # Controls whether the administrator may edit the fields of the cronjob. <canBeDisabled> # Controls whether the administrator may disable the cronjob. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"cronjob"},{"location":"package/pip/cronjob/#cronjob-package-installation-plugin","text":"Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past.","title":"Cronjob Package Installation Plugin"},{"location":"package/pip/cronjob/#components","text":"Each cronjob is described as an <cronjob> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/cronjob/#classname","text":"The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface.","title":"&lt;classname&gt;"},{"location":"package/pip/cronjob/#description","text":"The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator.","title":"&lt;description&gt;"},{"location":"package/pip/cronjob/#start","text":"All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax.","title":"&lt;start*&gt;"},{"location":"package/pip/cronjob/#canbeedited","text":"Controls whether the administrator may edit the fields of the cronjob.","title":"&lt;canBeEdited&gt;"},{"location":"package/pip/cronjob/#canbedisabled","text":"Controls whether the administrator may disable the cronjob.","title":"&lt;canBeDisabled&gt;"},{"location":"package/pip/cronjob/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/cronjob/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"Example"},{"location":"package/pip/database/","text":"Database Package Installation Plugin # Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename. Execution environment # The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#database-package-installation-plugin","text":"Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#attributes","text":"","title":"Attributes"},{"location":"package/pip/database/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/database/#expected-value","text":"The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects.","title":"Expected value"},{"location":"package/pip/database/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename.","title":"Naming convention"},{"location":"package/pip/database/#execution-environment","text":"The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Execution environment"},{"location":"package/pip/event-listener/","text":"Event Listener Package Installation Plugin # Registers event listeners. An explanation of events and event listeners can be found here . Components # Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <eventclassname> # The event class name is the name of the class in which the event is fired. <eventname> # The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events. <listenerclassname> # The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface! <inherit> # The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value. <environment> # The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ). <nice> # The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed. <permissions> # The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"eventListener"},{"location":"package/pip/event-listener/#event-listener-package-installation-plugin","text":"Registers event listeners. An explanation of events and event listeners can be found here .","title":"Event Listener Package Installation Plugin"},{"location":"package/pip/event-listener/#components","text":"Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/event-listener/#eventclassname","text":"The event class name is the name of the class in which the event is fired.","title":"&lt;eventclassname&gt;"},{"location":"package/pip/event-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events.","title":"&lt;eventname&gt;"},{"location":"package/pip/event-listener/#listenerclassname","text":"The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface!","title":"&lt;listenerclassname&gt;"},{"location":"package/pip/event-listener/#inherit","text":"The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value.","title":"&lt;inherit&gt;"},{"location":"package/pip/event-listener/#environment","text":"The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ).","title":"&lt;environment&gt;"},{"location":"package/pip/event-listener/#nice","text":"The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/event-listener/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/event-listener/#permissions","text":"The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/event-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/file/","text":"File Package Installation Plugin # Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates . Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file. Example in package.xml # <instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"file"},{"location":"package/pip/file/#file-package-installation-plugin","text":"Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates .","title":"File Package Installation Plugin"},{"location":"package/pip/file/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/file/#example-in-packagexml","text":"<instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"Example in package.xml"},{"location":"package/pip/language/","text":"Language Package Installation Plugin # Registers new language items. Components # The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute. <category> # Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). <item> # Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"language"},{"location":"package/pip/language/#language-package-installation-plugin","text":"Registers new language items.","title":"Language Package Installation Plugin"},{"location":"package/pip/language/#components","text":"The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute.","title":"Components"},{"location":"package/pip/language/#category","text":"Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E).","title":"&lt;category&gt;"},{"location":"package/pip/language/#item","text":"Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting.","title":"&lt;item&gt;"},{"location":"package/pip/language/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"Example"},{"location":"package/pip/media-provider/","text":"Media Provider Package Installation Plugin # Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages. Components # Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist . <title> # The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users. <regex> # The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group. <className> # <className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider . <html> # <className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"mediaProvider"},{"location":"package/pip/media-provider/#media-provider-package-installation-plugin","text":"Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages.","title":"Media Provider Package Installation Plugin"},{"location":"package/pip/media-provider/#components","text":"Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist .","title":"Components"},{"location":"package/pip/media-provider/#title","text":"The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users.","title":"&lt;title&gt;"},{"location":"package/pip/media-provider/#regex","text":"The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group.","title":"&lt;regex&gt;"},{"location":"package/pip/media-provider/#classname","text":"<className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider .","title":"&lt;className&gt;"},{"location":"package/pip/media-provider/#html","text":"<className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} .","title":"&lt;html&gt;"},{"location":"package/pip/media-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu-item/","text":"Menu Item Package Installation Plugin # Adds menu items to existing menus. Components # Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard . <menu> # The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml . <title> # The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values. <page> # The page that the link should point to, requires the internal identifier set by creating a page through the page.xml . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menuItem"},{"location":"package/pip/menu-item/#menu-item-package-installation-plugin","text":"Adds menu items to existing menus.","title":"Menu Item Package Installation Plugin"},{"location":"package/pip/menu-item/#components","text":"Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard .","title":"Components"},{"location":"package/pip/menu-item/#menu","text":"The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml .","title":"&lt;menu&gt;"},{"location":"package/pip/menu-item/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values.","title":"&lt;title&gt;"},{"location":"package/pip/menu-item/#page","text":"The page that the link should point to, requires the internal identifier set by creating a page through the page.xml .","title":"&lt;page&gt;"},{"location":"package/pip/menu-item/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu/","text":"Menu Package Installation Plugin # Deploy and manage menus that can be placed anywhere on the site. Components # Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu . <title> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements. <box> # The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menu"},{"location":"package/pip/menu/#menu-package-installation-plugin","text":"Deploy and manage menus that can be placed anywhere on the site.","title":"Menu Package Installation Plugin"},{"location":"package/pip/menu/#components","text":"Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu .","title":"Components"},{"location":"package/pip/menu/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements.","title":"&lt;title&gt;"},{"location":"package/pip/menu/#box","text":"The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName","title":"&lt;box&gt;"},{"location":"package/pip/menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/object-type-definition/","text":"Object Type Definition Package Installation Plugin # Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system. Components # Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <interfacename> # Optional The name of the PHP interface objectTypes have to implement. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"objectTypeDefinition"},{"location":"package/pip/object-type-definition/#object-type-definition-package-installation-plugin","text":"Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system.","title":"Object Type Definition Package Installation Plugin"},{"location":"package/pip/object-type-definition/#components","text":"Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type-definition/#interfacename","text":"Optional The name of the PHP interface objectTypes have to implement.","title":"&lt;interfacename&gt;"},{"location":"package/pip/object-type-definition/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"Example"},{"location":"package/pip/object-type/","text":"Object Type Package Installation Plugin # Registers an object type. Read about object types in the objectTypeDefinition PIP. Components # Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <definitionname> # The <name> of the objectTypeDefinition . <classname> # The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition. <*> # Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"objectType"},{"location":"package/pip/object-type/#object-type-package-installation-plugin","text":"Registers an object type. Read about object types in the objectTypeDefinition PIP.","title":"Object Type Package Installation Plugin"},{"location":"package/pip/object-type/#components","text":"Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type/#definitionname","text":"The <name> of the objectTypeDefinition .","title":"&lt;definitionname&gt;"},{"location":"package/pip/object-type/#classname","text":"The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition.","title":"&lt;classname&gt;"},{"location":"package/pip/object-type/#_1","text":"Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/object-type/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"Example"},{"location":"package/pip/option/","text":"Option Package Installation Plugin # Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants. Category Components # Each category is described as an <category> element with the mandatory attribute name . <parent> # Optional The category\u2019s parent category. <showorder> # Optional Specifies the order of this option within the parent category. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator. Option Components # Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it. <categoryname> # The option\u2019s category. <optiontype> # The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes. <defaultvalue> # The value that is set after installation of a package. Valid values are defined by the optiontype . <validationpattern> # Optional Defines a regular expression that is used to validate the value of a free form option (such as text ). <showorder> # Optional Specifies the order of this option within the category. <selectoptions> # Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code. <enableoptions> # Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option. <hidden> # Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator. <supporti18n> # Optional Specifies whether this option supports localized input. <requirei18n> # Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language). <*> # Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation. Language Items # All relevant language items have to be put into the wcf.acp.option language item category. Categories # If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"option"},{"location":"package/pip/option/#option-package-installation-plugin","text":"Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants.","title":"Option Package Installation Plugin"},{"location":"package/pip/option/#category-components","text":"Each category is described as an <category> element with the mandatory attribute name .","title":"Category Components"},{"location":"package/pip/option/#parent","text":"Optional The category\u2019s parent category.","title":"&lt;parent&gt;"},{"location":"package/pip/option/#showorder","text":"Optional Specifies the order of this option within the parent category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#option-components","text":"Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it.","title":"Option Components"},{"location":"package/pip/option/#categoryname","text":"The option\u2019s category.","title":"&lt;categoryname&gt;"},{"location":"package/pip/option/#optiontype","text":"The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes.","title":"&lt;optiontype&gt;"},{"location":"package/pip/option/#defaultvalue","text":"The value that is set after installation of a package. Valid values are defined by the optiontype .","title":"&lt;defaultvalue&gt;"},{"location":"package/pip/option/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of a free form option (such as text ).","title":"&lt;validationpattern&gt;"},{"location":"package/pip/option/#showorder_1","text":"Optional Specifies the order of this option within the category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#selectoptions","text":"Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code.","title":"&lt;selectoptions&gt;"},{"location":"package/pip/option/#enableoptions","text":"Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option.","title":"&lt;enableoptions&gt;"},{"location":"package/pip/option/#hidden","text":"Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically.","title":"&lt;hidden&gt;"},{"location":"package/pip/option/#options_1","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#supporti18n","text":"Optional Specifies whether this option supports localized input.","title":"&lt;supporti18n&gt;"},{"location":"package/pip/option/#requirei18n","text":"Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language).","title":"&lt;requirei18n&gt;"},{"location":"package/pip/option/#_1","text":"Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/option/#language-items","text":"All relevant language items have to be put into the wcf.acp.option language item category.","title":"Language Items"},{"location":"package/pip/option/#categories","text":"If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/option/#options_2","text":"If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description .","title":"Options"},{"location":"package/pip/option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/page/","text":"Page Package Installation Plugin # Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus. Components # Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList . <pageType> # system # The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <controller> # Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm . <handler> # Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id. <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <parent> # Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy. <hasFixedParent> # Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable. <permissions> # The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set. <options> # The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set. <excludeFromLandingPage> # Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the page title shown to the end users. <content> # The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"page"},{"location":"package/pip/page/#page-package-installation-plugin","text":"Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus.","title":"Page Package Installation Plugin"},{"location":"package/pip/page/#components","text":"Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList .","title":"Components"},{"location":"package/pip/page/#pagetype","text":"","title":"&lt;pageType&gt;"},{"location":"package/pip/page/#system","text":"The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element.","title":"system"},{"location":"package/pip/page/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/page/#controller","text":"Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm .","title":"&lt;controller&gt;"},{"location":"package/pip/page/#handler","text":"Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id.","title":"&lt;handler&gt;"},{"location":"package/pip/page/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/page/#parent","text":"Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy.","title":"&lt;parent&gt;"},{"location":"package/pip/page/#hasfixedparent","text":"Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable.","title":"&lt;hasFixedParent&gt;"},{"location":"package/pip/page/#permissions","text":"The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set.","title":"&lt;permissions&gt;"},{"location":"package/pip/page/#options","text":"The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set.","title":"&lt;options&gt;"},{"location":"package/pip/page/#excludefromlandingpage","text":"Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever.","title":"&lt;excludeFromLandingPage&gt;"},{"location":"package/pip/page/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/page/#title","text":"The title element is required and controls the page title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/page/#content_1","text":"The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/page/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"Example"},{"location":"package/pip/pip/","text":"Package Installation Plugin Package Installation Plugin # Registers new package installation plugins. Components # Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation! Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"pip"},{"location":"package/pip/pip/#package-installation-plugin-package-installation-plugin","text":"Registers new package installation plugins.","title":"Package Installation Plugin Package Installation Plugin"},{"location":"package/pip/pip/#components","text":"Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation!","title":"Components"},{"location":"package/pip/pip/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"Example"},{"location":"package/pip/script/","text":"Script Package Installation Plugin # Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The script -PIP expects a relative path to a .php file. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . Execution environment # The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"script"},{"location":"package/pip/script/#script-package-installation-plugin","text":"Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Script Package Installation Plugin"},{"location":"package/pip/script/#attributes","text":"","title":"Attributes"},{"location":"package/pip/script/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/script/#expected-value","text":"The script -PIP expects a relative path to a .php file.","title":"Expected value"},{"location":"package/pip/script/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 .","title":"Naming convention"},{"location":"package/pip/script/#execution-environment","text":"The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"Execution environment"},{"location":"package/pip/smiley/","text":"Smiley Package Installation Plugin # Installs new smileys. Components # Each smiley is described as an <smiley> element with the mandatory attribute name . <title> # Short human readable description of the smiley. <path(2x)?> # The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens. <aliases> # Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A). <showorder> # Optional Determines at which position of the smiley list the smiley is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"smiley"},{"location":"package/pip/smiley/#smiley-package-installation-plugin","text":"Installs new smileys.","title":"Smiley Package Installation Plugin"},{"location":"package/pip/smiley/#components","text":"Each smiley is described as an <smiley> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/smiley/#title","text":"Short human readable description of the smiley.","title":"&lt;title&gt;"},{"location":"package/pip/smiley/#path2x","text":"The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens.","title":"&lt;path(2x)?&gt;"},{"location":"package/pip/smiley/#aliases","text":"Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A).","title":"&lt;aliases&gt;"},{"location":"package/pip/smiley/#showorder","text":"Optional Determines at which position of the smiley list the smiley is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/smiley/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"Example"},{"location":"package/pip/sql/","text":"SQL Package Installation Plugin # Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard. Expected Value # The sql package installation plugin expects a relative path to a .sql file. Features # Logging # WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY Instance Number # It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 ! Table Type # WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used. Limitations # Logging # WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE . Triggers # WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script . Example # package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"sql"},{"location":"package/pip/sql/#sql-package-installation-plugin","text":"Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard.","title":"SQL Package Installation Plugin"},{"location":"package/pip/sql/#expected-value","text":"The sql package installation plugin expects a relative path to a .sql file.","title":"Expected Value"},{"location":"package/pip/sql/#features","text":"","title":"Features"},{"location":"package/pip/sql/#logging","text":"WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY","title":"Logging"},{"location":"package/pip/sql/#instance-number","text":"It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 !","title":"Instance Number"},{"location":"package/pip/sql/#table-type","text":"WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used.","title":"Table Type"},{"location":"package/pip/sql/#limitations","text":"","title":"Limitations"},{"location":"package/pip/sql/#logging_1","text":"WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE .","title":"Logging"},{"location":"package/pip/sql/#triggers","text":"WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script .","title":"Triggers"},{"location":"package/pip/sql/#example","text":"package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"Example"},{"location":"package/pip/style/","text":"Style Package Installation Plugin # Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles. Example in package.xml # <instruction type= \"style\" > style.tgz </instruction>","title":"style"},{"location":"package/pip/style/#style-package-installation-plugin","text":"Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles.","title":"Style Package Installation Plugin"},{"location":"package/pip/style/#example-in-packagexml","text":"<instruction type= \"style\" > style.tgz </instruction>","title":"Example in package.xml"},{"location":"package/pip/template-listener/","text":"Template Listener Package Installation Plugin # Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables). Components # Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <templatename> # The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners. <eventname> # The event name is the name given when the event is fired to identify different events within the same template. <templatecode> # The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention. <environment> # The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ). <nice> # Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"templateListener"},{"location":"package/pip/template-listener/#template-listener-package-installation-plugin","text":"Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables).","title":"Template Listener Package Installation Plugin"},{"location":"package/pip/template-listener/#components","text":"Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/template-listener/#templatename","text":"The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners.","title":"&lt;templatename&gt;"},{"location":"package/pip/template-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same template.","title":"&lt;eventname&gt;"},{"location":"package/pip/template-listener/#templatecode","text":"The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention.","title":"&lt;templatecode&gt;"},{"location":"package/pip/template-listener/#environment","text":"The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ).","title":"&lt;environment&gt;"},{"location":"package/pip/template-listener/#nice","text":"Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/template-listener/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/template-listener/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/template-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/template/","text":"Template Package Installation Plugin # Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"template"},{"location":"package/pip/template/#template-package-installation-plugin","text":"Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"Template Package Installation Plugin"},{"location":"package/pip/user-group-option/","text":"User Group Option Package Installation Plugin # Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <(admin|mod|user)defaultvalue> # Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS . <usersonly> # Makes the option unavailable for groups with the group type UserGroup::GUESTS . Language Items # All relevant language items have to be put into the wcf.acp.group language item category. Categories # If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"userGroupOption"},{"location":"package/pip/user-group-option/#user-group-option-package-installation-plugin","text":"Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Group Option Package Installation Plugin"},{"location":"package/pip/user-group-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-group-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-group-option/#adminmoduserdefaultvalue","text":"Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS .","title":"&lt;(admin|mod|user)defaultvalue&gt;"},{"location":"package/pip/user-group-option/#usersonly","text":"Makes the option unavailable for groups with the group type UserGroup::GUESTS .","title":"&lt;usersonly&gt;"},{"location":"package/pip/user-group-option/#language-items","text":"All relevant language items have to be put into the wcf.acp.group language item category.","title":"Language Items"},{"location":"package/pip/user-group-option/#categories","text":"If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-group-option/#options","text":"If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"Options"},{"location":"package/pip/user-menu/","text":"User Menu Package Installation Plugin # Registers new user menu items. Components # Each item is described as an <usermenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <iconclassname> # Use an icon only for top-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown. <classname> # The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"userMenu"},{"location":"package/pip/user-menu/#user-menu-package-installation-plugin","text":"Registers new user menu items.","title":"User Menu Package Installation Plugin"},{"location":"package/pip/user-menu/#components","text":"Each item is described as an <usermenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/user-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/user-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/user-menu/#iconclassname","text":"Use an icon only for top-level items. Name of the Font Awesome icon class.","title":"&lt;iconclassname&gt;"},{"location":"package/pip/user-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-menu/#classname","text":"The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"Example"},{"location":"package/pip/user-notification-event/","text":"User Notification Event Package Installation Plugin # Registers new user notification events. Components # Each package installation plugin is described as an <event> element with the mandatory child <name> . <objectType> # The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition. <classname> # The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface. <preset> # Defines whether this event is enabled by default. <presetmailnotificationtype> # Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"userNotificationEvent"},{"location":"package/pip/user-notification-event/#user-notification-event-package-installation-plugin","text":"Registers new user notification events.","title":"User Notification Event Package Installation Plugin"},{"location":"package/pip/user-notification-event/#components","text":"Each package installation plugin is described as an <event> element with the mandatory child <name> .","title":"Components"},{"location":"package/pip/user-notification-event/#objecttype","text":"The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition.","title":"&lt;objectType&gt;"},{"location":"package/pip/user-notification-event/#classname","text":"The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-notification-event/#preset","text":"Defines whether this event is enabled by default.","title":"&lt;preset&gt;"},{"location":"package/pip/user-notification-event/#presetmailnotificationtype","text":"Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default.","title":"&lt;presetmailnotificationtype&gt;"},{"location":"package/pip/user-notification-event/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available.","title":"&lt;options&gt;"},{"location":"package/pip/user-notification-event/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-notification-event/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"Example"},{"location":"package/pip/user-option/","text":"User Option Package Installation Plugin # Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <required> # Requires that a value is provided. <askduringregistration> # If set to 1 the field is shown during user registration in the frontend. <editable> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4 <visible> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8 <searchable> # If set to 1 the field is searchable. <outputclass> # PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface. Language Items # All relevant language items have to be put into the wcf.user.option language item category. Categories # If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"userOption"},{"location":"package/pip/user-option/#user-option-package-installation-plugin","text":"Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Option Package Installation Plugin"},{"location":"package/pip/user-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-option/#required","text":"Requires that a value is provided.","title":"&lt;required&gt;"},{"location":"package/pip/user-option/#askduringregistration","text":"If set to 1 the field is shown during user registration in the frontend.","title":"&lt;askduringregistration&gt;"},{"location":"package/pip/user-option/#editable","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4","title":"&lt;editable&gt;"},{"location":"package/pip/user-option/#visible","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8","title":"&lt;visible&gt;"},{"location":"package/pip/user-option/#searchable","text":"If set to 1 the field is searchable.","title":"&lt;searchable&gt;"},{"location":"package/pip/user-option/#outputclass","text":"PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface.","title":"&lt;outputclass&gt;"},{"location":"package/pip/user-option/#language-items","text":"All relevant language items have to be put into the wcf.user.option language item category.","title":"Language Items"},{"location":"package/pip/user-option/#categories","text":"If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-option/#options","text":"If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"Options"},{"location":"package/pip/user-profile-menu/","text":"User Profile Menu Package Installation Plugin # Registers new user profile tabs. Components # Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name . <classname> # The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface. <showorder> # Optional Determines at which position of the tab list the tab is shown. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"userProfileMenu"},{"location":"package/pip/user-profile-menu/#user-profile-menu-package-installation-plugin","text":"Registers new user profile tabs.","title":"User Profile Menu Package Installation Plugin"},{"location":"package/pip/user-profile-menu/#components","text":"Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-profile-menu/#classname","text":"The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-profile-menu/#showorder","text":"Optional Determines at which position of the tab list the tab is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-profile-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-profile-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-profile-menu/#example","text":"<?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"Example"},{"location":"php/apps/","text":"Apps for WoltLab Suite # Introduction # Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No. Differences to Plugins # Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link. Unique Namespace # Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" /> Unique Database Table Prefix # All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number. Separate Domain and Path Configuration # Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself. Creating an App # This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa. package.xml # The package.xml supports two additional elements in the <packageinformation> block that are unique to applications. <isapplication>1</isapplication> # This element is responsible to flag a package as an app. <applicationdirectory>example</applicationdirectory> # Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead. Minimum Required Files # An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Apps"},{"location":"php/apps/#apps-for-woltlab-suite","text":"","title":"Apps for WoltLab Suite"},{"location":"php/apps/#introduction","text":"Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No.","title":"Introduction"},{"location":"php/apps/#differences-to-plugins","text":"Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link.","title":"Differences to Plugins"},{"location":"php/apps/#unique-namespace","text":"Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" />","title":"Unique Namespace"},{"location":"php/apps/#unique-database-table-prefix","text":"All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number.","title":"Unique Database Table Prefix"},{"location":"php/apps/#separate-domain-and-path-configuration","text":"Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself.","title":"Separate Domain and Path Configuration"},{"location":"php/apps/#creating-an-app","text":"This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa.","title":"Creating an App"},{"location":"php/apps/#packagexml","text":"The package.xml supports two additional elements in the <packageinformation> block that are unique to applications.","title":"package.xml"},{"location":"php/apps/#isapplication1isapplication","text":"This element is responsible to flag a package as an app.","title":"&lt;isapplication&gt;1&lt;/isapplication&gt;"},{"location":"php/apps/#applicationdirectoryexampleapplicationdirectory","text":"Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead.","title":"&lt;applicationdirectory&gt;example&lt;/applicationdirectory&gt;"},{"location":"php/apps/#minimum-required-files","text":"An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Minimum Required Files"},{"location":"php/code-style-documentation/","text":"Documentation # The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. Database Objects # Database Table Columns as Properties # As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type Database Object Editors # Class Tags # Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself. Runtime Caches # Class Tags # Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Documentation"},{"location":"php/code-style-documentation/#documentation","text":"The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so.","title":"Documentation"},{"location":"php/code-style-documentation/#database-objects","text":"","title":"Database Objects"},{"location":"php/code-style-documentation/#database-table-columns-as-properties","text":"As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type","title":"Database Table Columns as Properties"},{"location":"php/code-style-documentation/#database-object-editors","text":"","title":"Database Object Editors"},{"location":"php/code-style-documentation/#class-tags","text":"Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself.","title":"Class Tags"},{"location":"php/code-style-documentation/#runtime-caches","text":"","title":"Runtime Caches"},{"location":"php/code-style-documentation/#class-tags_1","text":"Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Class Tags"},{"location":"php/code-style/","text":"Code Style # The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page . General Code Style # Naming conventions # The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING Arrays # For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ]; Ternary Operator # The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; Whitespaces # You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1) Classes # Referencing Class Names # If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ; Static Getters (of DatabaseObject Classes) # Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} . Long method calls # In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Code Style"},{"location":"php/code-style/#code-style","text":"The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page .","title":"Code Style"},{"location":"php/code-style/#general-code-style","text":"","title":"General Code Style"},{"location":"php/code-style/#naming-conventions","text":"The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING","title":"Naming conventions"},{"location":"php/code-style/#arrays","text":"For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ];","title":"Arrays"},{"location":"php/code-style/#ternary-operator","text":"The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ;","title":"Ternary Operator"},{"location":"php/code-style/#whitespaces","text":"You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1)","title":"Whitespaces"},{"location":"php/code-style/#classes","text":"","title":"Classes"},{"location":"php/code-style/#referencing-class-names","text":"If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ;","title":"Referencing Class Names"},{"location":"php/code-style/#static-getters-of-databaseobject-classes","text":"Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} .","title":"Static Getters (of DatabaseObject Classes)"},{"location":"php/code-style/#long-method-calls","text":"In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Long method calls"},{"location":"php/database-access/","text":"Database Access # Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works. The PreparedStatement Object # The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result } Query Parameters # The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result } Fetching a Single Result # Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor. Fetch by Column # There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn (); Fetching All Results # If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value. Building Complex Conditions # Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty! INSERT or UPDATE in Bulk # Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"Database Access"},{"location":"php/database-access/#database-access","text":"Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works.","title":"Database Access"},{"location":"php/database-access/#the-preparedstatement-object","text":"The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"The PreparedStatement Object"},{"location":"php/database-access/#query-parameters","text":"The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"Query Parameters"},{"location":"php/database-access/#fetching-a-single-result","text":"Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor.","title":"Fetching a Single Result"},{"location":"php/database-access/#fetch-by-column","text":"There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn ();","title":"Fetch by Column"},{"location":"php/database-access/#fetching-all-results","text":"If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value.","title":"Fetching All Results"},{"location":"php/database-access/#building-complex-conditions","text":"Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty!","title":"Building Complex Conditions"},{"location":"php/database-access/#insert-or-update-in-bulk","text":"Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"INSERT or UPDATE in Bulk"},{"location":"php/database-objects/","text":"Database Objects # WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text. DatabaseObject # The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually. DatabaseObjectDecorator # If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() . DatabaseObjectEditor # This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model. Create a new row # Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ; Updating an existing row # The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ; Deleting a row # Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete (); DatabaseObjectList # Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; } AbstractDatabaseObjectAction # Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; } Executing an Action # The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction Custom Method with AJAX Support # This section is about adding the method baz() to ExampleAction and calling it via AJAX. AJAX Validation # Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever! create, update and delete # The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } } Allow Invokation by Guests # Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods. ACP Access Only # Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"Database Objects"},{"location":"php/database-objects/#database-objects","text":"WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text.","title":"Database Objects"},{"location":"php/database-objects/#databaseobject","text":"The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually.","title":"DatabaseObject"},{"location":"php/database-objects/#databaseobjectdecorator","text":"If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() .","title":"DatabaseObjectDecorator"},{"location":"php/database-objects/#databaseobjecteditor","text":"This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model.","title":"DatabaseObjectEditor"},{"location":"php/database-objects/#create-a-new-row","text":"Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ;","title":"Create a new row"},{"location":"php/database-objects/#updating-an-existing-row","text":"The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ;","title":"Updating an existing row"},{"location":"php/database-objects/#deleting-a-row","text":"Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete ();","title":"Deleting a row"},{"location":"php/database-objects/#databaseobjectlist","text":"Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; }","title":"DatabaseObjectList"},{"location":"php/database-objects/#abstractdatabaseobjectaction","text":"Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; }","title":"AbstractDatabaseObjectAction"},{"location":"php/database-objects/#executing-an-action","text":"The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction","title":"Executing an Action"},{"location":"php/database-objects/#custom-method-with-ajax-support","text":"This section is about adding the method baz() to ExampleAction and calling it via AJAX.","title":"Custom Method with AJAX Support"},{"location":"php/database-objects/#ajax-validation","text":"Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever!","title":"AJAX Validation"},{"location":"php/database-objects/#create-update-and-delete","text":"The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } }","title":"create, update and delete"},{"location":"php/database-objects/#allow-invokation-by-guests","text":"Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods.","title":"Allow Invokation by Guests"},{"location":"php/database-objects/#acp-access-only","text":"Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"ACP Access Only"},{"location":"php/exceptions/","text":"Exceptions # SPL Exceptions # The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible. Custom Exceptions # Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Exceptions"},{"location":"php/exceptions/#exceptions","text":"","title":"Exceptions"},{"location":"php/exceptions/#spl-exceptions","text":"The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible.","title":"SPL Exceptions"},{"location":"php/exceptions/#custom-exceptions","text":"Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Custom Exceptions"},{"location":"php/gdpr/","text":"General Data Protection Regulation (GDPR) # Introduction # The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com. Including Data in the Export # The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property. Example code # <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } } $data # Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app. $exportUserProperties # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content. $exportUserPropertiesIfNotEmpty # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output. $exportUserOptionSettings # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content. $exportUserOptionSettingsIfNotEmpty # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. $ipAddresses # List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported. $skipUserOptions # All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"GDPR"},{"location":"php/gdpr/#general-data-protection-regulation-gdpr","text":"","title":"General Data Protection Regulation (GDPR)"},{"location":"php/gdpr/#introduction","text":"The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com.","title":"Introduction"},{"location":"php/gdpr/#including-data-in-the-export","text":"The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property.","title":"Including Data in the Export"},{"location":"php/gdpr/#example-code","text":"<? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } }","title":"Example code"},{"location":"php/gdpr/#data","text":"Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app.","title":"$data"},{"location":"php/gdpr/#exportuserproperties","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content.","title":"$exportUserProperties"},{"location":"php/gdpr/#exportuserpropertiesifnotempty","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output.","title":"$exportUserPropertiesIfNotEmpty"},{"location":"php/gdpr/#exportuseroptionsettings","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content.","title":"$exportUserOptionSettings"},{"location":"php/gdpr/#exportuseroptionsettingsifnotempty","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data.","title":"$exportUserOptionSettingsIfNotEmpty"},{"location":"php/gdpr/#ipaddresses","text":"List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported.","title":"$ipAddresses"},{"location":"php/gdpr/#skipuseroptions","text":"All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"$skipUserOptions"},{"location":"php/pages/","text":"Page Types # AbstractPage # The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow. Method Chain # __run() # This is the only method being invoked from the outside and starts the whole chain. readParameters() # Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters show() # Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show readData() # Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData assignVariables() # Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables AbstractForm # Extends the AbstractPage implementation with additional methods designed to handle form submissions properly. Method Chain # __run() # Inherited from AbstractPage. readParameters() # Inherited from AbstractPage. show() # Inherited from AbstractPage. submit() # The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit readFormParameters() # This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters validate() # Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate save() # Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save saved() # This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved readData() # Inherited from AbstractPage. assignVariables() # Inherited from AbstractPage.","title":"Pages"},{"location":"php/pages/#page-types","text":"","title":"Page Types"},{"location":"php/pages/#abstractpage","text":"The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow.","title":"AbstractPage"},{"location":"php/pages/#method-chain","text":"","title":"Method Chain"},{"location":"php/pages/#__run","text":"This is the only method being invoked from the outside and starts the whole chain.","title":"__run()"},{"location":"php/pages/#readparameters","text":"Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters","title":"readParameters()"},{"location":"php/pages/#show","text":"Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show","title":"show()"},{"location":"php/pages/#readdata","text":"Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData","title":"readData()"},{"location":"php/pages/#assignvariables","text":"Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables","title":"assignVariables()"},{"location":"php/pages/#abstractform","text":"Extends the AbstractPage implementation with additional methods designed to handle form submissions properly.","title":"AbstractForm"},{"location":"php/pages/#method-chain_1","text":"","title":"Method Chain"},{"location":"php/pages/#__run_1","text":"Inherited from AbstractPage.","title":"__run()"},{"location":"php/pages/#readparameters_1","text":"Inherited from AbstractPage.","title":"readParameters()"},{"location":"php/pages/#show_1","text":"Inherited from AbstractPage.","title":"show()"},{"location":"php/pages/#submit","text":"The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit","title":"submit()"},{"location":"php/pages/#readformparameters","text":"This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters","title":"readFormParameters()"},{"location":"php/pages/#validate","text":"Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate","title":"validate()"},{"location":"php/pages/#save","text":"Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save","title":"save()"},{"location":"php/pages/#saved","text":"This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved","title":"saved()"},{"location":"php/pages/#readdata_1","text":"Inherited from AbstractPage.","title":"readData()"},{"location":"php/pages/#assignvariables_1","text":"Inherited from AbstractPage.","title":"assignVariables()"},{"location":"php/api/caches/","text":"Caches # WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request. Understanding Caching # Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem. When to Use a Cache # It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild. When not to Use a Cache # I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"Caches"},{"location":"php/api/caches/#caches","text":"WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request.","title":"Caches"},{"location":"php/api/caches/#understanding-caching","text":"Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem.","title":"Understanding Caching"},{"location":"php/api/caches/#when-to-use-a-cache","text":"It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild.","title":"When to Use a Cache"},{"location":"php/api/caches/#when-not-to-use-a-cache","text":"I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"When not to Use a Cache"},{"location":"php/api/caches_persistent-caches/","text":"Persistent Caches # Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision. AbstractCacheBuilder # Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters ); getData(array $parameters = [], string $arrayIndex = ''): array # Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead. getMaxLifetime(): int # Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated. reset(array $parameters = []): void # Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() . rebuild(array $parameters): array # This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#persistent-caches","text":"Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#abstractcachebuilder","text":"Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters );","title":"AbstractCacheBuilder"},{"location":"php/api/caches_persistent-caches/#getdataarray-parameters-string-arrayindex-array","text":"Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead.","title":"getData(array $parameters = [], string $arrayIndex = ''): array"},{"location":"php/api/caches_persistent-caches/#getmaxlifetime-int","text":"Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated.","title":"getMaxLifetime(): int"},{"location":"php/api/caches_persistent-caches/#resetarray-parameters-void","text":"Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() .","title":"reset(array $parameters = []): void"},{"location":"php/api/caches_persistent-caches/#rebuildarray-parameters-array","text":"This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"rebuild(array $parameters): array"},{"location":"php/api/caches_runtime-caches/","text":"Runtime Caches # Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again. IRuntimeCache # Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ). Usage # <? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]); Example # <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#runtime-caches","text":"Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again.","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#iruntimecache","text":"Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ).","title":"IRuntimeCache"},{"location":"php/api/caches_runtime-caches/#usage","text":"<? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]);","title":"Usage"},{"location":"php/api/caches_runtime-caches/#example","text":"<? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Example"},{"location":"php/api/comments/","text":"Comments # User Group Options # You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above. Language Items # User Group Options # The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"Comments"},{"location":"php/api/comments/#comments","text":"","title":"Comments"},{"location":"php/api/comments/#user-group-options","text":"You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above.","title":"User Group Options"},{"location":"php/api/comments/#language-items","text":"","title":"Language Items"},{"location":"php/api/comments/#user-group-options_1","text":"The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"User Group Options"},{"location":"php/api/cronjobs/","text":"Cronjobs # Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob. Example # <? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } } ICronjob Interface # Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ). Executing Cronjobs Through CLI # Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Cronjobs"},{"location":"php/api/cronjobs/#cronjobs","text":"Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob.","title":"Cronjobs"},{"location":"php/api/cronjobs/#example","text":"<? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } }","title":"Example"},{"location":"php/api/cronjobs/#icronjob-interface","text":"Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ).","title":"ICronjob Interface"},{"location":"php/api/cronjobs/#executing-cronjobs-through-cli","text":"Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Executing Cronjobs Through CLI"},{"location":"php/api/event_list/","text":"Event List # Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name. WoltLab Suite Core # Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput * WoltLab Suite Forum # Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"Event List"},{"location":"php/api/event_list/#event-list","text":"Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name.","title":"Event List"},{"location":"php/api/event_list/#woltlab-suite-core","text":"Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput *","title":"WoltLab Suite Core"},{"location":"php/api/event_list/#woltlab-suite-forum","text":"Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"WoltLab Suite Forum"},{"location":"php/api/events/","text":"Events # WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here . Introductory Example # Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled. Listening to Events # In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty. Firing Events # If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards. Example: Using $parameters argument # Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } } Advanced Example: Additional Form Field # One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Events"},{"location":"php/api/events/#events","text":"WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here .","title":"Events"},{"location":"php/api/events/#introductory-example","text":"Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled.","title":"Introductory Example"},{"location":"php/api/events/#listening-to-events","text":"In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty.","title":"Listening to Events"},{"location":"php/api/events/#firing-events","text":"If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards.","title":"Firing Events"},{"location":"php/api/events/#example-using-parameters-argument","text":"Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } }","title":"Example: Using $parameters argument"},{"location":"php/api/events/#advanced-example-additional-form-field","text":"One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Advanced Example: Additional Form Field"},{"location":"php/api/package_installation_plugins/","text":"Package Installation Plugins # A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation. AbstractPackageInstallationPlugin # Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes. Class Members # Package Installation Plugins have a few notable class members easing your work: $installation # This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() . $application # Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored. AbstractXMLPackageInstallationPlugin # AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data. Class Members # $className # Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects. $tagName # Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object. prepareImport(array $data) # The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ]; validateImport(array $data) # The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid. findExistingItem(array $data) # The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query. 2.5.3. Example # <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ]; handleDelete(array $items) # The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); } postImport() # Allows you to (optionally) run additionally actions after all elements were processed. AbstractOptionPackageInstallationPlugin # AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options Differences to AbstractXMLPackageInstallationPlugin # $reservedTags # $reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#package-installation-plugins","text":"A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#abstractpackageinstallationplugin","text":"Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes.","title":"AbstractPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members","text":"Package Installation Plugins have a few notable class members easing your work:","title":"Class Members"},{"location":"php/api/package_installation_plugins/#installation","text":"This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() .","title":"$installation"},{"location":"php/api/package_installation_plugins/#application","text":"Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored.","title":"$application"},{"location":"php/api/package_installation_plugins/#abstractxmlpackageinstallationplugin","text":"AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data.","title":"AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members_1","text":"","title":"Class Members"},{"location":"php/api/package_installation_plugins/#classname","text":"Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects.","title":"$className"},{"location":"php/api/package_installation_plugins/#tagname","text":"Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object.","title":"$tagName"},{"location":"php/api/package_installation_plugins/#prepareimportarray-data","text":"The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ];","title":"prepareImport(array $data)"},{"location":"php/api/package_installation_plugins/#validateimportarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid.","title":"validateImport(array $data)"},{"location":"php/api/package_installation_plugins/#findexistingitemarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query.","title":"findExistingItem(array $data)"},{"location":"php/api/package_installation_plugins/#253-example","text":"<? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ];","title":"2.5.3. Example"},{"location":"php/api/package_installation_plugins/#handledeletearray-items","text":"The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); }","title":"handleDelete(array $items)"},{"location":"php/api/package_installation_plugins/#postimport","text":"Allows you to (optionally) run additionally actions after all elements were processed.","title":"postImport()"},{"location":"php/api/package_installation_plugins/#abstractoptionpackageinstallationplugin","text":"AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options","title":"AbstractOptionPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#differences-to-abstractxmlpackageinstallationplugin","text":"","title":"Differences to AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#reservedtags","text":"$reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"$reservedTags"},{"location":"php/api/sitemaps/","text":"Sitemaps # This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/sitemaps/#sitemaps","text":"This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/user_activity_points/","text":"User Activity Points # Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_activity_points/#user-activity-points","text":"Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_notifications/","text":"User Notifications # WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis. objectType.xml # For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id. userNotificationEvent.xml # Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ). Firing Events # When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs ); Marking Notifications as Confirmed # In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"User Notifications"},{"location":"php/api/user_notifications/#user-notifications","text":"WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis.","title":"User Notifications"},{"location":"php/api/user_notifications/#objecttypexml","text":"For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id.","title":"objectType.xml"},{"location":"php/api/user_notifications/#usernotificationeventxml","text":"Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ).","title":"userNotificationEvent.xml"},{"location":"php/api/user_notifications/#firing-events","text":"When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs );","title":"Firing Events"},{"location":"php/api/user_notifications/#marking-notifications-as-confirmed","text":"In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"Marking Notifications as Confirmed"},{"location":"php/api/form_builder/dependencies/","text":"Form Node Dependencies # Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript. IFormFieldDependency # The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects. Default Dependencies # WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values. JavaScript Implementation # To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract . Examples # If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Dependencies"},{"location":"php/api/form_builder/dependencies/#form-node-dependencies","text":"Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript.","title":"Form Node Dependencies"},{"location":"php/api/form_builder/dependencies/#iformfielddependency","text":"The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects.","title":"IFormFieldDependency"},{"location":"php/api/form_builder/dependencies/#default-dependencies","text":"WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values.","title":"Default Dependencies"},{"location":"php/api/form_builder/dependencies/#javascript-implementation","text":"To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract .","title":"JavaScript Implementation"},{"location":"php/api/form_builder/dependencies/#examples","text":"If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Examples"},{"location":"php/api/form_builder/form_fields/","text":"Form Builder Fields # Abstract Form Fields # The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes. AbstractFormField # AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class. AbstractNumericFormField # AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any . General Form Fields # The following form fields are general reusable fields without any underlying context. BooleanFormField # BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField . CheckboxFormField # Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox. ClassNameFormField # ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively). DateFormField # DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled. DescriptionFormField # DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label. EmailFormField # EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField . FloatFormField # FloatFormField is an implementation of AbstractNumericFormField for floating point numbers. IconFormField # IconFormField is a form field to select a FontAwesome icon. IntegerFormField # IntegerFormField is an implementation of AbstractNumericFormField for integers. IsDisabledFormField # IsDisabledFormField is a boolean form field with isDisabled as the default id. ItemListFormField # ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key. MultilineTextFormField # MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered. MultipleSelectionFormField # MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value. RadioButtonFormField # RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField . RatingFormField # RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange . ShowOrderFormField # ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering. SingleSelectionFormField # SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value. SortOrderFormField # SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending . TextFormField # TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField . TitleFormField # TitleFormField is a text form field with title as the default id and wcf.global.title as the default label. UrlFormField # UrlFormField is a text form field whose values are checked via Url::is() . Specific Fields # The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation. AclFormField # AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately. ButtonFormField # Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked. CaptchaFormField # CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method. ContentLanguageFormField # ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField . LabelFormField # LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used. OptionFormField # OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options . SimpleAclFormField # SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. SingleMediaSelectionFormField # SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set. TagFormField # TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key. UploadFormField # UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed. Provide value from database object # To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files. Process files # To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded. UserFormField # UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method. UserPasswordField # Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField UserGroupOptionFormField # UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions . UsernameFormField # UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length. Wysiwyg form container # To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container. WysiwygAttachmentFormField # WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments. WysiwygPollFormContainer # WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built. WysiwygSmileyFormContainer # WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category. WysiwygSmileyFormNode # WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer . Example # The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes () WysiwygFormField # WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. TWysiwygFormNode # All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id. Single-Use Form Fields # The following form fields are specific for certain forms and hardly reusable in other contexts. BBCodeAttributesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode. DevtoolsProjectExcludedPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project. DevtoolsProjectInstructionsFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project. DevtoolsProjectOptionalPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project. DevtoolsProjectRequiredPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"Fields"},{"location":"php/api/form_builder/form_fields/#form-builder-fields","text":"","title":"Form Builder Fields"},{"location":"php/api/form_builder/form_fields/#abstract-form-fields","text":"The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes.","title":"Abstract Form Fields"},{"location":"php/api/form_builder/form_fields/#abstractformfield","text":"AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class.","title":"AbstractFormField"},{"location":"php/api/form_builder/form_fields/#abstractnumericformfield","text":"AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any .","title":"AbstractNumericFormField"},{"location":"php/api/form_builder/form_fields/#general-form-fields","text":"The following form fields are general reusable fields without any underlying context.","title":"General Form Fields"},{"location":"php/api/form_builder/form_fields/#booleanformfield","text":"BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField .","title":"BooleanFormField"},{"location":"php/api/form_builder/form_fields/#checkboxformfield","text":"Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox.","title":"CheckboxFormField"},{"location":"php/api/form_builder/form_fields/#classnameformfield","text":"ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively).","title":"ClassNameFormField"},{"location":"php/api/form_builder/form_fields/#dateformfield","text":"DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled.","title":"DateFormField"},{"location":"php/api/form_builder/form_fields/#descriptionformfield","text":"DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label.","title":"DescriptionFormField"},{"location":"php/api/form_builder/form_fields/#emailformfield","text":"EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField .","title":"EmailFormField"},{"location":"php/api/form_builder/form_fields/#floatformfield","text":"FloatFormField is an implementation of AbstractNumericFormField for floating point numbers.","title":"FloatFormField"},{"location":"php/api/form_builder/form_fields/#iconformfield","text":"IconFormField is a form field to select a FontAwesome icon.","title":"IconFormField"},{"location":"php/api/form_builder/form_fields/#integerformfield","text":"IntegerFormField is an implementation of AbstractNumericFormField for integers.","title":"IntegerFormField"},{"location":"php/api/form_builder/form_fields/#isdisabledformfield","text":"IsDisabledFormField is a boolean form field with isDisabled as the default id.","title":"IsDisabledFormField"},{"location":"php/api/form_builder/form_fields/#itemlistformfield","text":"ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key.","title":"ItemListFormField"},{"location":"php/api/form_builder/form_fields/#multilinetextformfield","text":"MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered.","title":"MultilineTextFormField"},{"location":"php/api/form_builder/form_fields/#multipleselectionformfield","text":"MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value.","title":"MultipleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#radiobuttonformfield","text":"RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField .","title":"RadioButtonFormField"},{"location":"php/api/form_builder/form_fields/#ratingformfield","text":"RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange .","title":"RatingFormField"},{"location":"php/api/form_builder/form_fields/#showorderformfield","text":"ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering.","title":"ShowOrderFormField"},{"location":"php/api/form_builder/form_fields/#singleselectionformfield","text":"SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value.","title":"SingleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#sortorderformfield","text":"SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending .","title":"SortOrderFormField"},{"location":"php/api/form_builder/form_fields/#textformfield","text":"TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField .","title":"TextFormField"},{"location":"php/api/form_builder/form_fields/#titleformfield","text":"TitleFormField is a text form field with title as the default id and wcf.global.title as the default label.","title":"TitleFormField"},{"location":"php/api/form_builder/form_fields/#urlformfield","text":"UrlFormField is a text form field whose values are checked via Url::is() .","title":"UrlFormField"},{"location":"php/api/form_builder/form_fields/#specific-fields","text":"The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation.","title":"Specific Fields"},{"location":"php/api/form_builder/form_fields/#aclformfield","text":"AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately.","title":"AclFormField"},{"location":"php/api/form_builder/form_fields/#buttonformfield","text":"Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked.","title":"ButtonFormField"},{"location":"php/api/form_builder/form_fields/#captchaformfield","text":"CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method.","title":"CaptchaFormField"},{"location":"php/api/form_builder/form_fields/#contentlanguageformfield","text":"ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField .","title":"ContentLanguageFormField"},{"location":"php/api/form_builder/form_fields/#labelformfield","text":"LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used.","title":"LabelFormField"},{"location":"php/api/form_builder/form_fields/#optionformfield","text":"OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options .","title":"OptionFormField"},{"location":"php/api/form_builder/form_fields/#simpleaclformfield","text":"SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"SimpleAclFormField"},{"location":"php/api/form_builder/form_fields/#singlemediaselectionformfield","text":"SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set.","title":"SingleMediaSelectionFormField"},{"location":"php/api/form_builder/form_fields/#tagformfield","text":"TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key.","title":"TagFormField"},{"location":"php/api/form_builder/form_fields/#uploadformfield","text":"UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed.","title":"UploadFormField"},{"location":"php/api/form_builder/form_fields/#provide-value-from-database-object","text":"To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files.","title":"Provide value from database object"},{"location":"php/api/form_builder/form_fields/#process-files","text":"To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded.","title":"Process files"},{"location":"php/api/form_builder/form_fields/#userformfield","text":"UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method.","title":"UserFormField"},{"location":"php/api/form_builder/form_fields/#userpasswordfield","text":"Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField","title":"UserPasswordField"},{"location":"php/api/form_builder/form_fields/#usergroupoptionformfield","text":"UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions .","title":"UserGroupOptionFormField"},{"location":"php/api/form_builder/form_fields/#usernameformfield","text":"UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length.","title":"UsernameFormField"},{"location":"php/api/form_builder/form_fields/#wysiwyg-form-container","text":"To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container.","title":"Wysiwyg form container"},{"location":"php/api/form_builder/form_fields/#wysiwygattachmentformfield","text":"WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments.","title":"WysiwygAttachmentFormField"},{"location":"php/api/form_builder/form_fields/#wysiwygpollformcontainer","text":"WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built.","title":"WysiwygPollFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformcontainer","text":"WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category.","title":"WysiwygSmileyFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformnode","text":"WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer .","title":"WysiwygSmileyFormNode"},{"location":"php/api/form_builder/form_fields/#example","text":"The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes ()","title":"Example"},{"location":"php/api/form_builder/form_fields/#wysiwygformfield","text":"WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"WysiwygFormField"},{"location":"php/api/form_builder/form_fields/#twysiwygformnode","text":"All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id.","title":"TWysiwygFormNode"},{"location":"php/api/form_builder/form_fields/#single-use-form-fields","text":"The following form fields are specific for certain forms and hardly reusable in other contexts.","title":"Single-Use Form Fields"},{"location":"php/api/form_builder/form_fields/#bbcodeattributesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode.","title":"BBCodeAttributesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectexcludedpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project.","title":"DevtoolsProjectExcludedPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectinstructionsformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project.","title":"DevtoolsProjectInstructionsFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectoptionalpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project.","title":"DevtoolsProjectOptionalPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectrequiredpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"DevtoolsProjectRequiredPackagesFormField"},{"location":"php/api/form_builder/overview/","text":"Form Builder # Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar. Advantages of Form Builder # WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form. Form Builder Components # Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems. AbstractFormBuilderForm # To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object. DialogFormDocument # Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"Overview"},{"location":"php/api/form_builder/overview/#form-builder","text":"Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar.","title":"Form Builder"},{"location":"php/api/form_builder/overview/#advantages-of-form-builder","text":"WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form.","title":"Advantages of Form Builder"},{"location":"php/api/form_builder/overview/#form-builder-components","text":"Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems.","title":"Form Builder Components"},{"location":"php/api/form_builder/overview/#abstractformbuilderform","text":"To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object.","title":"AbstractFormBuilderForm"},{"location":"php/api/form_builder/overview/#dialogformdocument","text":"Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"DialogFormDocument"},{"location":"php/api/form_builder/structure/","text":"Structure of Form Builder # Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining. Form Nodes # IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label. IFormNode # IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods. IFormChildNode # IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() . IFormParentNode # IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children. IFormElement # IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element. IObjectTypeFormNode # IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods. CustomFormNode # CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used. TemplateFormNode # TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template. Form Document # A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable. Form Button # A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element. Form Container # A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container. Form Field # A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here . Form Field Interfaces and Traits # WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields: IAttributeFormField # Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used. IAutoCompleteFormField # Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens. IAutoFocusFormField # IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods. ICssClassFormField # Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods. IFileFormField # IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ). IFilterableSelectionFormField # IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods. II18nFormField # II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() . IImmutableFormField # IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable. IInputModeFormField # Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods. IMaximumFormField # IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually. IMaximumLengthFormField # IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() . IMinimumFormField # IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually. IMinimumLengthFormField # IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() . IMultipleFormField # IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually. INullableFormField # INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable. IPackagesFormField # IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods. IPatternFormField # Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods. IPlaceholderFormField # IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods. ISelectionFormField # ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods. ISuffixedFormField # ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods. TDefaultIdFormField # Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() . Displaying Forms # The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Structure"},{"location":"php/api/form_builder/structure/#structure-of-form-builder","text":"Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining.","title":"Structure of Form Builder"},{"location":"php/api/form_builder/structure/#form-nodes","text":"IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label.","title":"Form Nodes"},{"location":"php/api/form_builder/structure/#iformnode","text":"IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods.","title":"IFormNode"},{"location":"php/api/form_builder/structure/#iformchildnode","text":"IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() .","title":"IFormChildNode"},{"location":"php/api/form_builder/structure/#iformparentnode","text":"IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children.","title":"IFormParentNode"},{"location":"php/api/form_builder/structure/#iformelement","text":"IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element.","title":"IFormElement"},{"location":"php/api/form_builder/structure/#iobjecttypeformnode","text":"IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods.","title":"IObjectTypeFormNode"},{"location":"php/api/form_builder/structure/#customformnode","text":"CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used.","title":"CustomFormNode"},{"location":"php/api/form_builder/structure/#templateformnode","text":"TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template.","title":"TemplateFormNode"},{"location":"php/api/form_builder/structure/#form-document","text":"A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable.","title":"Form Document"},{"location":"php/api/form_builder/structure/#form-button","text":"A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element.","title":"Form Button"},{"location":"php/api/form_builder/structure/#form-container","text":"A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container.","title":"Form Container"},{"location":"php/api/form_builder/structure/#form-field","text":"A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here .","title":"Form Field"},{"location":"php/api/form_builder/structure/#form-field-interfaces-and-traits","text":"WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields:","title":"Form Field Interfaces and Traits"},{"location":"php/api/form_builder/structure/#iattributeformfield","text":"Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used.","title":"IAttributeFormField"},{"location":"php/api/form_builder/structure/#iautocompleteformfield","text":"Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens.","title":"IAutoCompleteFormField"},{"location":"php/api/form_builder/structure/#iautofocusformfield","text":"IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods.","title":"IAutoFocusFormField"},{"location":"php/api/form_builder/structure/#icssclassformfield","text":"Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods.","title":"ICssClassFormField"},{"location":"php/api/form_builder/structure/#ifileformfield","text":"IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ).","title":"IFileFormField"},{"location":"php/api/form_builder/structure/#ifilterableselectionformfield","text":"IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods.","title":"IFilterableSelectionFormField"},{"location":"php/api/form_builder/structure/#ii18nformfield","text":"II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() .","title":"II18nFormField"},{"location":"php/api/form_builder/structure/#iimmutableformfield","text":"IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable.","title":"IImmutableFormField"},{"location":"php/api/form_builder/structure/#iinputmodeformfield","text":"Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods.","title":"IInputModeFormField"},{"location":"php/api/form_builder/structure/#imaximumformfield","text":"IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually.","title":"IMaximumFormField"},{"location":"php/api/form_builder/structure/#imaximumlengthformfield","text":"IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() .","title":"IMaximumLengthFormField"},{"location":"php/api/form_builder/structure/#iminimumformfield","text":"IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually.","title":"IMinimumFormField"},{"location":"php/api/form_builder/structure/#iminimumlengthformfield","text":"IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() .","title":"IMinimumLengthFormField"},{"location":"php/api/form_builder/structure/#imultipleformfield","text":"IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually.","title":"IMultipleFormField"},{"location":"php/api/form_builder/structure/#inullableformfield","text":"INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable.","title":"INullableFormField"},{"location":"php/api/form_builder/structure/#ipackagesformfield","text":"IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods.","title":"IPackagesFormField"},{"location":"php/api/form_builder/structure/#ipatternformfield","text":"Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods.","title":"IPatternFormField"},{"location":"php/api/form_builder/structure/#iplaceholderformfield","text":"IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods.","title":"IPlaceholderFormField"},{"location":"php/api/form_builder/structure/#iselectionformfield","text":"ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods.","title":"ISelectionFormField"},{"location":"php/api/form_builder/structure/#isuffixedformfield","text":"ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods.","title":"ISuffixedFormField"},{"location":"php/api/form_builder/structure/#tdefaultidformfield","text":"Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() .","title":"TDefaultIdFormField"},{"location":"php/api/form_builder/structure/#displaying-forms","text":"The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Displaying Forms"},{"location":"php/api/form_builder/validation_data/","text":"Form Validation and Form Data # Form Validation # Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface. IFormFieldValidationError / FormFieldValidationError # IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method. IFormFieldValidator / FormFieldValidator # IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method. Form Data # After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler. IFormDataHandler / FormDataHandler # IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly. IFormDataProcessor / DefaultFormDataProcessor # IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array. Additional Data Processors # CustomFormDataProcessor # As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged. VoidFormDataProcessor # Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"Validation and Data"},{"location":"php/api/form_builder/validation_data/#form-validation-and-form-data","text":"","title":"Form Validation and Form Data"},{"location":"php/api/form_builder/validation_data/#form-validation","text":"Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface.","title":"Form Validation"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidationerror-formfieldvalidationerror","text":"IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method.","title":"IFormFieldValidationError / FormFieldValidationError"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidator-formfieldvalidator","text":"IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method.","title":"IFormFieldValidator / FormFieldValidator"},{"location":"php/api/form_builder/validation_data/#form-data","text":"After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler.","title":"Form Data"},{"location":"php/api/form_builder/validation_data/#iformdatahandler-formdatahandler","text":"IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly.","title":"IFormDataHandler / FormDataHandler"},{"location":"php/api/form_builder/validation_data/#iformdataprocessor-defaultformdataprocessor","text":"IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array.","title":"IFormDataProcessor / DefaultFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#additional-data-processors","text":"","title":"Additional Data Processors"},{"location":"php/api/form_builder/validation_data/#customformdataprocessor","text":"As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged.","title":"CustomFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#voidformdataprocessor","text":"Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"VoidFormDataProcessor"},{"location":"tutorial/series/overview/","text":"Tutorial Series # In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Overview"},{"location":"tutorial/series/overview/#tutorial-series","text":"In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Tutorial Series"},{"location":"tutorial/series/part_1/","text":"Tutorial Series Part 1: Base Structure # In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions. Package Functionality # The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml Person Modeling # Database Table # As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL ); Database Object # Person # In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class. PersonAction # <? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP. PersonEditor # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name. PersonList # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class. ACP # Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu. ACP Menu # We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value. People List # To list the people in the ACP, we need a PersonListPage class and a personList template. PersonListPage # <? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns. personList.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people. Person Add Form # Like the person list, the form to add new people requires a controller class and a template. PersonAddForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases. personAdd.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted. Person Edit Form # As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing. PersonEditForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\" Frontend # For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu. page.xml # First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page . menuItem.xml # Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered. People List # As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories. PersonListPage # <? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default. personList.tpl # { capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events. userGroupOption.xml # We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 . package.xml # Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"Part 1"},{"location":"tutorial/series/part_1/#tutorial-series-part-1-base-structure","text":"In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions.","title":"Tutorial Series Part 1: Base Structure"},{"location":"tutorial/series/part_1/#package-functionality","text":"The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_1/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates .","title":"Used Components"},{"location":"tutorial/series/part_1/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml","title":"Package Structure"},{"location":"tutorial/series/part_1/#person-modeling","text":"","title":"Person Modeling"},{"location":"tutorial/series/part_1/#database-table","text":"As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL );","title":"Database Table"},{"location":"tutorial/series/part_1/#database-object","text":"","title":"Database Object"},{"location":"tutorial/series/part_1/#person","text":"In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class.","title":"Person"},{"location":"tutorial/series/part_1/#personaction","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP.","title":"PersonAction"},{"location":"tutorial/series/part_1/#personeditor","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name.","title":"PersonEditor"},{"location":"tutorial/series/part_1/#personlist","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class.","title":"PersonList"},{"location":"tutorial/series/part_1/#acp","text":"Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu.","title":"ACP"},{"location":"tutorial/series/part_1/#acp-menu","text":"We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value.","title":"ACP Menu"},{"location":"tutorial/series/part_1/#people-list","text":"To list the people in the ACP, we need a PersonListPage class and a personList template.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage","text":"<? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#person-add-form","text":"Like the person list, the form to add new people requires a controller class and a template.","title":"Person Add Form"},{"location":"tutorial/series/part_1/#personaddform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases.","title":"PersonAddForm"},{"location":"tutorial/series/part_1/#personaddtpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted.","title":"personAdd.tpl"},{"location":"tutorial/series/part_1/#person-edit-form","text":"As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing.","title":"Person Edit Form"},{"location":"tutorial/series/part_1/#personeditform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\"","title":"PersonEditForm"},{"location":"tutorial/series/part_1/#frontend","text":"For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu.","title":"Frontend"},{"location":"tutorial/series/part_1/#pagexml","text":"First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page .","title":"page.xml"},{"location":"tutorial/series/part_1/#menuitemxml","text":"Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered.","title":"menuItem.xml"},{"location":"tutorial/series/part_1/#people-list_1","text":"As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage_1","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl_1","text":"{ capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#usergroupoptionxml","text":"We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 .","title":"userGroupOption.xml"},{"location":"tutorial/series/part_1/#packagexml","text":"Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_2/","text":"Part 2: Event Listeners and Template Listeners # In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date. Package Functionality # The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl Extending Person Model ( install.sql ) # The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday . Setting Birthday in ACP # To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below . Adding Birthday Table Column in ACP # To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td> Adding Birthday in Front End # In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if } templateListener.xml # The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template. eventListener.xml # There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data> package.xml # The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"Part 2"},{"location":"tutorial/series/part_2/#part-2-event-listeners-and-template-listeners","text":"In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date.","title":"Part 2: Event Listeners and Template Listeners"},{"location":"tutorial/series/part_2/#package-functionality","text":"The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_2/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events .","title":"Used Components"},{"location":"tutorial/series/part_2/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl","title":"Package Structure"},{"location":"tutorial/series/part_2/#extending-person-model-installsql","text":"The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday .","title":"Extending Person Model (install.sql)"},{"location":"tutorial/series/part_2/#setting-birthday-in-acp","text":"To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below .","title":"Setting Birthday in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-table-column-in-acp","text":"To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td>","title":"Adding Birthday Table Column in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-in-front-end","text":"In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if }","title":"Adding Birthday in Front End"},{"location":"tutorial/series/part_2/#templatelistenerxml","text":"The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template.","title":"templateListener.xml"},{"location":"tutorial/series/part_2/#eventlistenerxml","text":"There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data>","title":"eventListener.xml"},{"location":"tutorial/series/part_2/#packagexml","text":"The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_3/","text":"Tutorial Series Part 3: Person Page and Comments # In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality. Package Functionality # In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages Used Components # In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler. Package Structure # The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub . Runtime Cache # To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; } Comments # To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments. Person Page # PersonPage # <? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments. person.tpl # {capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix. page.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent. PersonPageHandler # <? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"Part 3"},{"location":"tutorial/series/part_3/#tutorial-series-part-3-person-page-and-comments","text":"In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality.","title":"Tutorial Series Part 3: Person Page and Comments"},{"location":"tutorial/series/part_3/#package-functionality","text":"In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages","title":"Package Functionality"},{"location":"tutorial/series/part_3/#used-components","text":"In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler.","title":"Used Components"},{"location":"tutorial/series/part_3/#package-structure","text":"The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub .","title":"Package Structure"},{"location":"tutorial/series/part_3/#runtime-cache","text":"To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; }","title":"Runtime Cache"},{"location":"tutorial/series/part_3/#comments","text":"To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments.","title":"Comments"},{"location":"tutorial/series/part_3/#person-page","text":"","title":"Person Page"},{"location":"tutorial/series/part_3/#personpage","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments.","title":"PersonPage"},{"location":"tutorial/series/part_3/#persontpl","text":"{capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix.","title":"person.tpl"},{"location":"tutorial/series/part_3/#pagexml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent.","title":"page.xml"},{"location":"tutorial/series/part_3/#personpagehandler","text":"<? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"PersonPageHandler"},{"location":"view/css/","text":"CSS # SCSS and CSS # SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code. File Location # Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss . Variables # You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} . Linking images # Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash. Media Breakpoints # Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } } Available Breakpoints # Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px) Asset Preloading # WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"CSS"},{"location":"view/css/#css","text":"","title":"CSS"},{"location":"view/css/#scss-and-css","text":"SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code.","title":"SCSS and CSS"},{"location":"view/css/#file-location","text":"Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss .","title":"File Location"},{"location":"view/css/#variables","text":"You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} .","title":"Variables"},{"location":"view/css/#linking-images","text":"Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash.","title":"Linking images"},{"location":"view/css/#media-breakpoints","text":"Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } }","title":"Media Breakpoints"},{"location":"view/css/#available-breakpoints","text":"Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px)","title":"Available Breakpoints"},{"location":"view/css/#asset-preloading","text":"WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"Asset Preloading"},{"location":"view/languages-naming-conventions/","text":"Language Naming Conventions # This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments Forms # Fields # If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description . Error Texts # If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items. Naming Conventions # If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type. Confirmation messages # If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items. Type-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen? English # Do you really want delete the {element type}? Example: Do you really want delete the icon? Object-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen? English # Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>? User Group Options # Comments # German # group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren English # group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#language-naming-conventions","text":"This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#forms","text":"","title":"Forms"},{"location":"view/languages-naming-conventions/#fields","text":"If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description .","title":"Fields"},{"location":"view/languages-naming-conventions/#error-texts","text":"If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items.","title":"Error Texts"},{"location":"view/languages-naming-conventions/#naming-conventions","text":"If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type.","title":"Naming Conventions"},{"location":"view/languages-naming-conventions/#confirmation-messages","text":"If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items.","title":"Confirmation messages"},{"location":"view/languages-naming-conventions/#type-specific-deletion-confirmation-message","text":"","title":"Type-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english","text":"Do you really want delete the {element type}? Example: Do you really want delete the icon?","title":"English"},{"location":"view/languages-naming-conventions/#object-specific-deletion-confirmation-message","text":"","title":"Object-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german_1","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english_1","text":"Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>?","title":"English"},{"location":"view/languages-naming-conventions/#user-group-options","text":"","title":"User Group Options"},{"location":"view/languages-naming-conventions/#comments","text":"","title":"Comments"},{"location":"view/languages-naming-conventions/#german_2","text":"group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren","title":"German"},{"location":"view/languages-naming-conventions/#english_2","text":"group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"English"},{"location":"view/languages/","text":"Languages # WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items . Special Phrases # wcf.date.dateFormat # Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation. wcf.date.timeFormat # Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters. wcf.date.firstDayOfTheWeek # Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday wcf.global.pageDirection - RTL support # Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"Languages"},{"location":"view/languages/#languages","text":"WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items .","title":"Languages"},{"location":"view/languages/#special-phrases","text":"","title":"Special Phrases"},{"location":"view/languages/#wcfdatedateformat","text":"Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation.","title":"wcf.date.dateFormat"},{"location":"view/languages/#wcfdatetimeformat","text":"Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters.","title":"wcf.date.timeFormat"},{"location":"view/languages/#wcfdatefirstdayoftheweek","text":"Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday","title":"wcf.date.firstDayOfTheWeek"},{"location":"view/languages/#wcfglobalpagedirection-rtl-support","text":"Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"wcf.global.pageDirection - RTL support"},{"location":"view/template-plugins/","text":"Template Plugins # 5.3+ anchor # The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink . 5.3+ anchorAttributes # anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default append # If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array. assign # New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } capture # In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture } concat # concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * } counter # counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default 5.4+ csrfToken # {csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details. currency # currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * } cycle # cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter date # date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date } 3.1+ dateInterval # dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set) encodeJS # encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script> encodeJSON # encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } ' escapeCDATA # escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]> event # event provides extension points in templates that template listeners can use. { event name = 'foo' } fetch # fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * } filesizeBinary # filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary } filesize # filesize formats the filesize using filesize (in bytes). { $filesize | filesize } hascontent # In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent } htmlCheckboxes # htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options htmlOptions # htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element. implode # implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration 5.2+ ipSearch # ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch } 3.0+ js # js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * } 5.3+ jslang # jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); lang # lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item. language # language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language } link # link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() . newlineToBreak # newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak } 3.0+ page # page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID ) pages # pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links plainTime # plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime } 5.3+ plural # plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer. prepend # If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array. shortUnit # shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * } smallpages # smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links. tableWordwrap # tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap } time # time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * } truncate # truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default 5.3+ user # user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"Template Plugins"},{"location":"view/template-plugins/#template-plugins","text":"","title":"Template Plugins"},{"location":"view/template-plugins/#53-anchor","text":"The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink .","title":"5.3+ anchor"},{"location":"view/template-plugins/#53-anchorattributes","text":"anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default","title":"5.3+ anchorAttributes"},{"location":"view/template-plugins/#append","text":"If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array.","title":"append"},{"location":"view/template-plugins/#assign","text":"New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * }","title":"assign"},{"location":"view/template-plugins/#capture","text":"In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture }","title":"capture"},{"location":"view/template-plugins/#concat","text":"concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * }","title":"concat"},{"location":"view/template-plugins/#counter","text":"counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default","title":"counter"},{"location":"view/template-plugins/#54-csrftoken","text":"{csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details.","title":"5.4+ csrfToken"},{"location":"view/template-plugins/#currency","text":"currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * }","title":"currency"},{"location":"view/template-plugins/#cycle","text":"cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter","title":"cycle"},{"location":"view/template-plugins/#date","text":"date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date }","title":"date"},{"location":"view/template-plugins/#31-dateinterval","text":"dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set)","title":"3.1+ dateInterval"},{"location":"view/template-plugins/#encodejs","text":"encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script>","title":"encodeJS"},{"location":"view/template-plugins/#encodejson","text":"encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } '","title":"encodeJSON"},{"location":"view/template-plugins/#escapecdata","text":"escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]>","title":"escapeCDATA"},{"location":"view/template-plugins/#event","text":"event provides extension points in templates that template listeners can use. { event name = 'foo' }","title":"event"},{"location":"view/template-plugins/#fetch","text":"fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * }","title":"fetch"},{"location":"view/template-plugins/#filesizebinary","text":"filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary }","title":"filesizeBinary"},{"location":"view/template-plugins/#filesize","text":"filesize formats the filesize using filesize (in bytes). { $filesize | filesize }","title":"filesize"},{"location":"view/template-plugins/#hascontent","text":"In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent }","title":"hascontent"},{"location":"view/template-plugins/#htmlcheckboxes","text":"htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options","title":"htmlCheckboxes"},{"location":"view/template-plugins/#htmloptions","text":"htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element.","title":"htmlOptions"},{"location":"view/template-plugins/#implode","text":"implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration","title":"implode"},{"location":"view/template-plugins/#52-ipsearch","text":"ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch }","title":"5.2+ ipSearch"},{"location":"view/template-plugins/#30-js","text":"js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * }","title":"3.0+ js"},{"location":"view/template-plugins/#53-jslang","text":"jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } );","title":"5.3+ jslang"},{"location":"view/template-plugins/#lang","text":"lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item.","title":"lang"},{"location":"view/template-plugins/#language","text":"language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language }","title":"language"},{"location":"view/template-plugins/#link","text":"link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() .","title":"link"},{"location":"view/template-plugins/#newlinetobreak","text":"newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak }","title":"newlineToBreak"},{"location":"view/template-plugins/#30-page","text":"page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID )","title":"3.0+ page"},{"location":"view/template-plugins/#pages","text":"pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links","title":"pages"},{"location":"view/template-plugins/#plaintime","text":"plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime }","title":"plainTime"},{"location":"view/template-plugins/#53-plural","text":"plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer.","title":"5.3+ plural"},{"location":"view/template-plugins/#prepend","text":"If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array.","title":"prepend"},{"location":"view/template-plugins/#shortunit","text":"shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * }","title":"shortUnit"},{"location":"view/template-plugins/#smallpages","text":"smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links.","title":"smallpages"},{"location":"view/template-plugins/#tablewordwrap","text":"tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap }","title":"tableWordwrap"},{"location":"view/template-plugins/#time","text":"time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * }","title":"time"},{"location":"view/template-plugins/#truncate","text":"truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default","title":"truncate"},{"location":"view/template-plugins/#53-user","text":"user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"5.3+ user"},{"location":"view/templates/","text":"Templates # Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore. Template Types and Conventions # WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal. Installing Templates # Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages. Base Templates # Frontend # { include file = 'header' } { * content * } { include file = 'footer' } Backend # { include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page. Common Template Components # Forms # For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> Tab Menus # <div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div> Template Scripting # Template Variables # Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]); Modifiers # If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed. System Template Variable # The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend). Comments # Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template. Conditions # Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if } Foreach Loops # Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach } Including Other Templates # To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' } Template Plugins # An overview of all available template plugins can be found here .","title":"Templates"},{"location":"view/templates/#templates","text":"Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore.","title":"Templates"},{"location":"view/templates/#template-types-and-conventions","text":"WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal.","title":"Template Types and Conventions"},{"location":"view/templates/#installing-templates","text":"Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages.","title":"Installing Templates"},{"location":"view/templates/#base-templates","text":"","title":"Base Templates"},{"location":"view/templates/#frontend","text":"{ include file = 'header' } { * content * } { include file = 'footer' }","title":"Frontend"},{"location":"view/templates/#backend","text":"{ include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page.","title":"Backend"},{"location":"view/templates/#common-template-components","text":"","title":"Common Template Components"},{"location":"view/templates/#forms","text":"For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form>","title":"Forms"},{"location":"view/templates/#tab-menus","text":"<div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div>","title":"Tab Menus"},{"location":"view/templates/#template-scripting","text":"","title":"Template Scripting"},{"location":"view/templates/#template-variables","text":"Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]);","title":"Template Variables"},{"location":"view/templates/#modifiers","text":"If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed.","title":"Modifiers"},{"location":"view/templates/#system-template-variable","text":"The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend).","title":"System Template Variable"},{"location":"view/templates/#comments","text":"Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template.","title":"Comments"},{"location":"view/templates/#conditions","text":"Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if }","title":"Conditions"},{"location":"view/templates/#foreach-loops","text":"Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach }","title":"Foreach Loops"},{"location":"view/templates/#including-other-templates","text":"To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' }","title":"Including Other Templates"},{"location":"view/templates/#template-plugins","text":"An overview of all available template plugins can be found here .","title":"Template Plugins"}]}
\ No newline at end of file
index 3da30ec63c1d9d5119276da721a14d92460a0457..137f930e4e071735d5871498eef22dd94052d238 100644 (file)
 <?xml version="1.0" encoding="UTF-8"?>
 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url>
 </urlset>
\ No newline at end of file
index 2551044bd4175a2f29bf0f3c9d075563940cee3a..99bf4b32837859a153626dac90e3ab2b6b6d2266 100644 (file)
Binary files a/5.4/sitemap.xml.gz and b/5.4/sitemap.xml.gz differ
index bfd738f9164bf50ce3642343e438ec4eab96ed0d..b3235871ce39a698ff9d298f947751edb008d6b9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1846,6 +1842,7 @@ Note that in the context of this example, not every added feature might make per
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1910,10 +1907,10 @@ Note that in the context of this example, not every added feature might make per
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0da55947ad3362095e68f10e895ea7d0e2332789..e5d5abbc534e8cc8552f8d497f22d2d6589c9d3c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -3347,6 +3343,7 @@ As the menu item package installation plugin validates the given page and throws
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -3411,10 +3408,10 @@ As the menu item package installation plugin validates the given page and throws
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1c6652240b1888aa03005ee732f57797c3252c40..7587d728a178556843bc5073a307dd1d2806f8f9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2405,6 +2401,7 @@ In contrast, reading the existing birthday from a person is only relevant for ed
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2469,10 +2466,10 @@ In contrast, reading the existing birthday from a person is only relevant for ed
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index fe052c71d89bd2d158752073c19890532de9f837..b7c8e84f71f11c8dd8fed2c34428d2d89cba384f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2528,6 +2524,7 @@ The <code>IOnlineLocationPageHandler</code> interface requires two methods to be
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2578,10 +2575,10 @@ The <code>IOnlineLocationPageHandler</code> interface requires two methods to be
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 300012ec4eb9929e422fe141fe2aefc8ab60eac9..547716156e087537e6ead4e24ae4ab875b5ac11e 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2105,6 +2101,7 @@ To communicate the preloading intent to the compiler, the <code>--woltlab-suite-
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2169,10 +2166,10 @@ To communicate the preloading intent to the compiler, the <code>--woltlab-suite-
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 02673b8df968a3540b6565e084cc94dc838cf77d..5db76b5b0f2349e1c203dfa1a962d2ec6417f61c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2125,6 +2121,7 @@ There are several general error messages like <code>wcf.global.form.error.empty<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2156,10 +2153,10 @@ There are several general error messages like <code>wcf.global.form.error.empty<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index de65a9710ebbd9d9131acc25ce9e1a5272f53067..1738b40d77d640a126d9cc4d9a405b062c660ca7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1977,6 +1973,7 @@ direction.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2041,10 +2038,10 @@ direction.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b2e4ca9a31c85368d3e22c7b9fe5bfe2e8f765ff..4247b1fe7c2012917cbe0c1a15ebf7d601616b96 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2888,6 +2884,7 @@ The optional <code>type</code> parameter is responsible for what the generated l
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2919,10 +2916,10 @@ The optional <code>type</code> parameter is responsible for what the generated l
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index de3bb72d6089fb523ccb15c79259222b338c6f25..ea0e906ce64bbab44030399e4a8bb2987fa08123 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2393,6 +2389,7 @@ If the foreach loop has a name assigned to it via the <code>name</code> attribut
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2457,10 +2454,10 @@ If the foreach loop has a name assigned to it via the <code>name</code> attribut
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b275a4214f9fb83ebf2eb391c943645ecceb81f1..4ac2f3b00cd39c6ae4843167e84d8fb87b8a1337 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="/assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="/assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="/assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="/assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="/assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="/assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("/",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="/." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="/." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="/assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="/." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="/." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="/assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "/", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "/assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "/", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "/assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="/assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="/assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
diff --git a/latest/assets/javascripts/bundle.5cf3e710.min.js b/latest/assets/javascripts/bundle.5cf3e710.min.js
deleted file mode 100644 (file)
index b728e04..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-(()=>{var qo=Object.create,Ct=Object.defineProperty,Ko=Object.getPrototypeOf,cr=Object.prototype.hasOwnProperty,Yo=Object.getOwnPropertyNames,Jo=Object.getOwnPropertyDescriptor,ur=Object.getOwnPropertySymbols,Xo=Object.prototype.propertyIsEnumerable;var N=Object.assign,Bo=e=>Ct(e,"__esModule",{value:!0});var lr=(e,t)=>{var r={};for(var n in e)cr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&ur)for(var n of ur(e))t.indexOf(n)<0&&Xo.call(e,n)&&(r[n]=e[n]);return r},jt=(e,t)=>()=>(t||(t={exports:{}},e(t.exports,t)),t.exports);var Go=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Yo(t))!cr.call(e,n)&&n!=="default"&&Ct(e,n,{get:()=>t[n],enumerable:!(r=Jo(t,n))||r.enumerable});return e},it=e=>e&&e.__esModule?e:Go(Bo(Ct(e!=null?qo(Ko(e)):{},"default",{value:e,enumerable:!0})),e);var pr=jt((kt,fr)=>{(function(e,t){typeof kt=="object"&&typeof fr!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kt,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function c(S){return!!(S&&S!==document&&S.nodeName!=="HTML"&&S.nodeName!=="BODY"&&"classList"in S&&"contains"in S.classList)}function u(S){var Ye=S.type,Ht=S.tagName;return!!(Ht==="INPUT"&&a[Ye]&&!S.readOnly||Ht==="TEXTAREA"&&!S.readOnly||S.isContentEditable)}function s(S){S.classList.contains("focus-visible")||(S.classList.add("focus-visible"),S.setAttribute("data-focus-visible-added",""))}function l(S){!S.hasAttribute("data-focus-visible-added")||(S.classList.remove("focus-visible"),S.removeAttribute("data-focus-visible-added"))}function p(S){S.metaKey||S.altKey||S.ctrlKey||(c(r.activeElement)&&s(r.activeElement),n=!0)}function d(S){n=!1}function _(S){!c(S.target)||(n||u(S.target))&&s(S.target)}function $(S){!c(S.target)||(S.target.classList.contains("focus-visible")||S.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),l(S.target))}function A(S){document.visibilityState==="hidden"&&(o&&(n=!0),Z())}function Z(){document.addEventListener("mousemove",F),document.addEventListener("mousedown",F),document.addEventListener("mouseup",F),document.addEventListener("pointermove",F),document.addEventListener("pointerdown",F),document.addEventListener("pointerup",F),document.addEventListener("touchmove",F),document.addEventListener("touchstart",F),document.addEventListener("touchend",F)}function P(){document.removeEventListener("mousemove",F),document.removeEventListener("mousedown",F),document.removeEventListener("mouseup",F),document.removeEventListener("pointermove",F),document.removeEventListener("pointerdown",F),document.removeEventListener("pointerup",F),document.removeEventListener("touchmove",F),document.removeEventListener("touchstart",F),document.removeEventListener("touchend",F)}function F(S){S.target.nodeName&&S.target.nodeName.toLowerCase()==="html"||(n=!1,P())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",A,!0),Z(),r.addEventListener("focus",_,!0),r.addEventListener("blur",$,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Bt=jt((ot,Xt)=>{(function(t,r){typeof ot=="object"&&typeof Xt=="object"?Xt.exports=r():typeof define=="function"&&define.amd?define([],r):typeof ot=="object"?ot.ClipboardJS=r():t.ClipboardJS=r()})(ot,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(n,o,i){r.o(n,o)||Object.defineProperty(n,o,{enumerable:!0,get:i})},r.r=function(n){typeof Symbol!="undefined"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},r.t=function(n,o){if(o&1&&(n=r(n)),o&8||o&4&&typeof n=="object"&&n&&n.__esModule)return n;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),o&2&&typeof n!="string")for(var a in n)r.d(i,a,function(c){return n[c]}.bind(null,a));return i},r.n=function(n){var o=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(o,"a",o),o},r.o=function(n,o){return Object.prototype.hasOwnProperty.call(n,o)},r.p="",r(r.s=6)}([function(e,t){function r(n){var o;if(n.nodeName==="SELECT")n.focus(),o=n.value;else if(n.nodeName==="INPUT"||n.nodeName==="TEXTAREA"){var i=n.hasAttribute("readonly");i||n.setAttribute("readonly",""),n.select(),n.setSelectionRange(0,n.value.length),i||n.removeAttribute("readonly"),o=n.value}else{n.hasAttribute("contenteditable")&&n.focus();var a=window.getSelection(),c=document.createRange();c.selectNodeContents(n),a.removeAllRanges(),a.addRange(c),o=a.toString()}return o}e.exports=r},function(e,t){function r(){}r.prototype={on:function(n,o,i){var a=this.e||(this.e={});return(a[n]||(a[n]=[])).push({fn:o,ctx:i}),this},once:function(n,o,i){var a=this;function c(){a.off(n,c),o.apply(i,arguments)}return c._=o,this.on(n,c,i)},emit:function(n){var o=[].slice.call(arguments,1),i=((this.e||(this.e={}))[n]||[]).slice(),a=0,c=i.length;for(a;a<c;a++)i[a].fn.apply(i[a].ctx,o);return this},off:function(n,o){var i=this.e||(this.e={}),a=i[n],c=[];if(a&&o)for(var u=0,s=a.length;u<s;u++)a[u].fn!==o&&a[u].fn._!==o&&c.push(a[u]);return c.length?i[n]=c:delete i[n],this}},e.exports=r,e.exports.TinyEmitter=r},function(e,t,r){var n=r(3),o=r(4);function i(s,l,p){if(!s&&!l&&!p)throw new Error("Missing required arguments");if(!n.string(l))throw new TypeError("Second argument must be a String");if(!n.fn(p))throw new TypeError("Third argument must be a Function");if(n.node(s))return a(s,l,p);if(n.nodeList(s))return c(s,l,p);if(n.string(s))return u(s,l,p);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function a(s,l,p){return s.addEventListener(l,p),{destroy:function(){s.removeEventListener(l,p)}}}function c(s,l,p){return Array.prototype.forEach.call(s,function(d){d.addEventListener(l,p)}),{destroy:function(){Array.prototype.forEach.call(s,function(d){d.removeEventListener(l,p)})}}}function u(s,l,p){return o(document.body,s,l,p)}e.exports=i},function(e,t){t.node=function(r){return r!==void 0&&r instanceof HTMLElement&&r.nodeType===1},t.nodeList=function(r){var n=Object.prototype.toString.call(r);return r!==void 0&&(n==="[object NodeList]"||n==="[object HTMLCollection]")&&"length"in r&&(r.length===0||t.node(r[0]))},t.string=function(r){return typeof r=="string"||r instanceof String},t.fn=function(r){var n=Object.prototype.toString.call(r);return n==="[object Function]"}},function(e,t,r){var n=r(5);function o(c,u,s,l,p){var d=a.apply(this,arguments);return c.addEventListener(s,d,p),{destroy:function(){c.removeEventListener(s,d,p)}}}function i(c,u,s,l,p){return typeof c.addEventListener=="function"?o.apply(null,arguments):typeof s=="function"?o.bind(null,document).apply(null,arguments):(typeof c=="string"&&(c=document.querySelectorAll(c)),Array.prototype.map.call(c,function(d){return o(d,u,s,l,p)}))}function a(c,u,s,l){return function(p){p.delegateTarget=n(p.target,u),p.delegateTarget&&l.call(c,p)}}e.exports=i},function(e,t){var r=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}function o(i,a){for(;i&&i.nodeType!==r;){if(typeof i.matches=="function"&&i.matches(a))return i;i=i.parentNode}}e.exports=o},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),i=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(E){return typeof E}:function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},a=function(){function E(h,v){for(var y=0;y<v.length;y++){var L=v[y];L.enumerable=L.enumerable||!1,L.configurable=!0,"value"in L&&(L.writable=!0),Object.defineProperty(h,L.key,L)}}return function(h,v,y){return v&&E(h.prototype,v),y&&E(h,y),h}}();function c(E,h){if(!(E instanceof h))throw new TypeError("Cannot call a class as a function")}var u=function(){function E(h){c(this,E),this.resolveOptions(h),this.initSelection()}return a(E,[{key:"resolveOptions",value:function(){var v=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=v.action,this.container=v.container,this.emitter=v.emitter,this.target=v.target,this.text=v.text,this.trigger=v.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var v=this,y=document.documentElement.getAttribute("dir")=="rtl";this.removeFake(),this.fakeHandlerCallback=function(){return v.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[y?"right":"left"]="-9999px";var L=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=L+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=o()(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=o()(this.target),this.copyText()}},{key:"copyText",value:function(){var v=void 0;try{v=document.execCommand(this.action)}catch(y){v=!1}this.handleResult(v)}},{key:"handleResult",value:function(v){this.emitter.emit(v?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var v=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"copy";if(this._action=v,this._action!=="copy"&&this._action!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(v){if(v!==void 0)if(v&&(typeof v=="undefined"?"undefined":i(v))==="object"&&v.nodeType===1){if(this.action==="copy"&&v.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(this.action==="cut"&&(v.hasAttribute("readonly")||v.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`);this._target=v}else throw new Error('Invalid "target" value, use a valid Element')},get:function(){return this._target}}]),E}(),s=u,l=r(1),p=r.n(l),d=r(2),_=r.n(d),$=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(E){return typeof E}:function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},A=function(){function E(h,v){for(var y=0;y<v.length;y++){var L=v[y];L.enumerable=L.enumerable||!1,L.configurable=!0,"value"in L&&(L.writable=!0),Object.defineProperty(h,L.key,L)}}return function(h,v,y){return v&&E(h.prototype,v),y&&E(h,y),h}}();function Z(E,h){if(!(E instanceof h))throw new TypeError("Cannot call a class as a function")}function P(E,h){if(!E)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return h&&(typeof h=="object"||typeof h=="function")?h:E}function F(E,h){if(typeof h!="function"&&h!==null)throw new TypeError("Super expression must either be null or a function, not "+typeof h);E.prototype=Object.create(h&&h.prototype,{constructor:{value:E,enumerable:!1,writable:!0,configurable:!0}}),h&&(Object.setPrototypeOf?Object.setPrototypeOf(E,h):E.__proto__=h)}var S=function(E){F(h,E);function h(v,y){Z(this,h);var L=P(this,(h.__proto__||Object.getPrototypeOf(h)).call(this));return L.resolveOptions(y),L.listenClick(v),L}return A(h,[{key:"resolveOptions",value:function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof y.action=="function"?y.action:this.defaultAction,this.target=typeof y.target=="function"?y.target:this.defaultTarget,this.text=typeof y.text=="function"?y.text:this.defaultText,this.container=$(y.container)==="object"?y.container:document.body}},{key:"listenClick",value:function(y){var L=this;this.listener=_()(y,"click",function(Je){return L.onClick(Je)})}},{key:"onClick",value:function(y){var L=y.delegateTarget||y.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s({action:this.action(L),target:this.target(L),text:this.text(L),container:this.container,trigger:L,emitter:this})}},{key:"defaultAction",value:function(y){return Ye("action",y)}},{key:"defaultTarget",value:function(y){var L=Ye("target",y);if(L)return document.querySelector(L)}},{key:"defaultText",value:function(y){return Ye("text",y)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],L=typeof y=="string"?[y]:y,Je=!!document.queryCommandSupported;return L.forEach(function(Qo){Je=Je&&!!document.queryCommandSupported(Qo)}),Je}}]),h}(p.a);function Ye(E,h){var v="data-clipboard-"+E;if(!!h.hasAttribute(v))return h.getAttribute(v)}var Ht=t.default=S}]).default})});var wo=jt((Yb,So)=>{"use strict";var Di=/["'&<>]/;So.exports=Ui;function Ui(e){var t=""+e,r=Di.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i<t.length;i++){switch(t.charCodeAt(i)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}a!==i&&(o+=t.substring(a,i)),a=i+1,o+=n}return a!==i?o+t.substring(a,i):o}});var rx=it(pr());var Ft=function(e,t){return Ft=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,n){r.__proto__=n}||function(r,n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(r[o]=n[o])},Ft(e,t)};function z(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");Ft(e,t);function r(){this.constructor=e}e.prototype=t===null?Object.create(t):(r.prototype=t.prototype,new r)}function mr(e,t,r,n){function o(i){return i instanceof r?i:new r(function(a){a(i)})}return new(r||(r=Promise))(function(i,a){function c(l){try{s(n.next(l))}catch(p){a(p)}}function u(l){try{s(n.throw(l))}catch(p){a(p)}}function s(l){l.done?i(l.value):o(l.value).then(c,u)}s((n=n.apply(e,t||[])).next())})}function dr(e,t){var r={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},n,o,i,a;return a={next:c(0),throw:c(1),return:c(2)},typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function c(s){return function(l){return u([s,l])}}function u(s){if(n)throw new TypeError("Generator is already executing.");for(;r;)try{if(n=1,o&&(i=s[0]&2?o.return:s[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,s[1])).done)return i;switch(o=0,i&&(s=[s[0]&2,i.value]),s[0]){case 0:case 1:i=s;break;case 4:return r.label++,{value:s[1],done:!1};case 5:r.label++,o=s[1],s=[0];continue;case 7:s=r.ops.pop(),r.trys.pop();continue;default:if(i=r.trys,!(i=i.length>0&&i[i.length-1])&&(s[0]===6||s[0]===2)){r=0;continue}if(s[0]===3&&(!i||s[1]>i[0]&&s[1]<i[3])){r.label=s[1];break}if(s[0]===6&&r.label<i[1]){r.label=i[1],i=s;break}if(i&&r.label<i[2]){r.label=i[2],r.ops.push(s);break}i[2]&&r.ops.pop(),r.trys.pop();continue}s=t.call(e,r)}catch(l){s=[6,l],o=0}finally{n=i=0}if(s[0]&5)throw s[1];return{value:s[0]?s[1]:void 0,done:!0}}}function ae(e){var t=typeof Symbol=="function"&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&typeof e.length=="number")return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function C(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(c){a={error:c}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function I(e,t){for(var r=0,n=t.length,o=e.length;r<n;r++,o++)e[o]=t[r];return e}function hr(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof ae=="function"?ae(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(c,u){a=e[i](a),o(c,u,a.done,a.value)})}}function o(i,a,c,u){Promise.resolve(u).then(function(s){i({value:s,done:c})},a)}}function g(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var st=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription:
-`+r.map(function(n,o){return o+1+") "+n.toString()}).join(`
-  `):"",this.name="UnsubscriptionError",this.errors=r}});function ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var te=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._teardowns=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(Array.isArray(a))try{for(var c=ae(a),u=c.next();!u.done;u=c.next()){var s=u.value;s.remove(this)}}catch(A){t={error:A}}finally{try{u&&!u.done&&(r=c.return)&&r.call(c)}finally{if(t)throw t.error}}else a==null||a.remove(this);var l=this.initialTeardown;if(g(l))try{l()}catch(A){i=A instanceof st?A.errors:[A]}var p=this._teardowns;if(p){this._teardowns=null;try{for(var d=ae(p),_=d.next();!_.done;_=d.next()){var $=_.value;try{br($)}catch(A){i=i!=null?i:[],A instanceof st?i=I(I([],C(i)),C(A.errors)):i.push(A)}}}catch(A){n={error:A}}finally{try{_&&!_.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new st(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)br(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._teardowns=(r=this._teardowns)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ve(r,t)},e.prototype.remove=function(t){var r=this._teardowns;r&&ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var It=te.EMPTY;function ct(e){return e instanceof te||e&&"closed"in e&&g(e.remove)&&g(e.add)&&g(e.unsubscribe)}function br(e){g(e)?e():e.unsubscribe()}var pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Re={setTimeout:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Re.delegate;return((r==null?void 0:r.setTimeout)||setTimeout).apply(void 0,I([],C(e)))},clearTimeout:function(e){var t=Re.delegate;return((t==null?void 0:t.clearTimeout)||clearTimeout)(e)},delegate:void 0};function ut(e){Re.setTimeout(function(){var t=pe.onUnhandledError;if(t)t(e);else throw e})}function Y(){}var vr=function(){return Rt("C",void 0,void 0)}();function gr(e){return Rt("E",void 0,e)}function yr(e){return Rt("N",e,void 0)}function Rt(e,t,r){return{kind:e,value:t,error:r}}var Xe=function(e){z(t,e);function t(r){var n=e.call(this)||this;return n.isStopped=!1,r?(n.destination=r,ct(r)&&r.add(n)):n.destination=Zo,n}return t.create=function(r,n,o){return new $t(r,n,o)},t.prototype.next=function(r){this.isStopped?Pt(yr(r),this):this._next(r)},t.prototype.error=function(r){this.isStopped?Pt(gr(r),this):(this.isStopped=!0,this._error(r))},t.prototype.complete=function(){this.isStopped?Pt(vr,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this))},t.prototype._next=function(r){this.destination.next(r)},t.prototype._error=function(r){this.destination.error(r),this.unsubscribe()},t.prototype._complete=function(){this.destination.complete(),this.unsubscribe()},t}(te);var $t=function(e){z(t,e);function t(r,n,o){var i=e.call(this)||this,a;if(g(r))a=r;else if(r){a=r.next,n=r.error,o=r.complete;var c;i&&pe.useDeprecatedNextContext?(c=Object.create(r),c.unsubscribe=function(){return i.unsubscribe()}):c=r,a=a==null?void 0:a.bind(c),n=n==null?void 0:n.bind(c),o=o==null?void 0:o.bind(c)}return i.destination={next:a?Vt(a,i):Y,error:Vt(n||xr,i),complete:o?Vt(o,i):Y},i}return t}(Xe);function Vt(e,t){return pe.useDeprecatedSynchronousErrorHandling?function(r){try{e(r)}catch(n){t.__syncError=n}}:e}function xr(e){if(pe.useDeprecatedSynchronousErrorHandling)throw e;ut(e)}function Pt(e,t){var r=pe.onStoppedNotification;r&&Re.setTimeout(function(){return r(e,t)})}var Zo={closed:!0,next:Y,error:xr,complete:Y};var Se=function(){return typeof Symbol=="function"&&Symbol.observable||"@@observable"}();function se(e){return e}function Sr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Dt(e)}function Dt(e){return e.length===0?se:e.length===1?e[0]:function(r){return e.reduce(function(n,o){return o(n)},r)}}var O=function(){function e(t){t&&(this._subscribe=t)}return e.prototype.lift=function(t){var r=new e;return r.source=this,r.operator=t,r},e.prototype.subscribe=function(t,r,n){var o=ei(t)?t:new $t(t,r,n),i=this,a=i.operator,c=i.source;if(o.add(a?a.call(o,c):c||pe.useDeprecatedSynchronousErrorHandling?this._subscribe(o):this._trySubscribe(o)),pe.useDeprecatedSynchronousErrorHandling)for(var u=o;u;){if(u.__syncError)throw u.__syncError;u=u.destination}return o},e.prototype._trySubscribe=function(t){try{return this._subscribe(t)}catch(r){t.error(r)}},e.prototype.forEach=function(t,r){var n=this;return r=wr(r),new r(function(o,i){var a;a=n.subscribe(function(c){try{t(c)}catch(u){i(u),a==null||a.unsubscribe()}},i,o)})},e.prototype._subscribe=function(t){var r;return(r=this.source)===null||r===void 0?void 0:r.subscribe(t)},e.prototype[Se]=function(){return this},e.prototype.pipe=function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return t.length?Dt(t)(this):this},e.prototype.toPromise=function(t){var r=this;return t=wr(t),new t(function(n,o){var i;r.subscribe(function(a){return i=a},function(a){return o(a)},function(){return n(i)})})},e.create=function(t){return new e(t)},e}();function wr(e){var t;return(t=e!=null?e:pe.Promise)!==null&&t!==void 0?t:Promise}function ti(e){return e&&g(e.next)&&g(e.error)&&g(e.complete)}function ei(e){return e&&e instanceof Xe||ti(e)&&ct(e)}function ri(e){return g(e==null?void 0:e.lift)}function m(e){return function(t){if(ri(t))return t.lift(function(r){try{return e(r,this)}catch(n){this.error(n)}});throw new TypeError("Unable to lift unknown Observable type")}}var b=function(e){z(t,e);function t(r,n,o,i,a){var c=e.call(this,r)||this;return c.onFinalize=a,c._next=n?function(u){try{n(u)}catch(s){this.destination.error(s)}}:e.prototype._next,c._error=o?function(u){try{o(u)}catch(s){this.destination.error(s)}this.unsubscribe()}:e.prototype._error,c._complete=i?function(){try{i()}catch(u){this.destination.error(u)}this.unsubscribe()}:e.prototype._complete,c}return t.prototype.unsubscribe=function(){var r,n=this.closed;e.prototype.unsubscribe.call(this),!n&&((r=this.onFinalize)===null||r===void 0||r.call(this))},t}(Xe);var $e={schedule:function(e){var t=requestAnimationFrame,r=cancelAnimationFrame,n=$e.delegate;n&&(t=n.requestAnimationFrame,r=n.cancelAnimationFrame);var o=t(function(i){r=void 0,e(i)});return new te(function(){return r==null?void 0:r(o)})},requestAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.requestAnimationFrame)||requestAnimationFrame).apply(void 0,I([],C(e)))},cancelAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.cancelAnimationFrame)||cancelAnimationFrame).apply(void 0,I([],C(e)))},delegate:void 0};var Er=at(function(e){return function(){e(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"}});var T=function(e){z(t,e);function t(){var r=e.call(this)||this;return r.observers=[],r.closed=!1,r.isStopped=!1,r.hasError=!1,r.thrownError=null,r}return t.prototype.lift=function(r){var n=new Or(this,this);return n.operator=r,n},t.prototype._throwIfClosed=function(){if(this.closed)throw new Er},t.prototype.next=function(r){var n,o;if(this._throwIfClosed(),!this.isStopped){var i=this.observers.slice();try{for(var a=ae(i),c=a.next();!c.done;c=a.next()){var u=c.value;u.next(r)}}catch(s){n={error:s}}finally{try{c&&!c.done&&(o=a.return)&&o.call(a)}finally{if(n)throw n.error}}}},t.prototype.error=function(r){if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=r;for(var n=this.observers;n.length;)n.shift().error(r)}},t.prototype.complete=function(){if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;for(var r=this.observers;r.length;)r.shift().complete()}},t.prototype.unsubscribe=function(){this.isStopped=this.closed=!0,this.observers=null},t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,c=o.observers;return i||a?It:(c.push(r),new te(function(){return ve(n.observers,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new O;return r.source=this,r},t.create=function(r,n){return new Or(r,n)},t}(O);var Or=function(e){z(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:It},t}(T);var Be={now:function(){return(Be.delegate||Date).now()},delegate:void 0};var lt=function(e){z(t,e);function t(r,n,o){r===void 0&&(r=Infinity),n===void 0&&(n=Infinity),o===void 0&&(o=Be);var i=e.call(this)||this;return i.bufferSize=r,i.windowTime=n,i.timestampProvider=o,i.buffer=[],i.infiniteTimeWindow=!0,i.infiniteTimeWindow=n===Infinity,i.bufferSize=Math.max(1,r),i.windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n.buffer,a=n.infiniteTimeWindow,c=n.timestampProvider,u=n.windowTime;o||(i.push(r),!a&&i.push(c.now()+u)),this.trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this.trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o.infiniteTimeWindow,a=o.buffer,c=a.slice(),u=0;u<c.length&&!r.closed;u+=i?1:2)r.next(c[u]);return this._checkFinalizedStatuses(r),n},t.prototype.trimBuffer=function(){var r=this,n=r.bufferSize,o=r.timestampProvider,i=r.buffer,a=r.infiniteTimeWindow,c=(a?1:2)*n;if(n<Infinity&&c<i.length&&i.splice(0,i.length-c),!a){for(var u=o.now(),s=0,l=1;l<i.length&&i[l]<=u;l+=2)s=l;s&&i.splice(0,s+1)}},t}(T);var Tr=function(e){z(t,e);function t(r,n){return e.call(this)||this}return t.prototype.schedule=function(r,n){return n===void 0&&(n=0),this},t}(te);var Ge={setInterval:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ge.delegate;return((r==null?void 0:r.setInterval)||setInterval).apply(void 0,I([],C(e)))},clearInterval:function(e){var t=Ge.delegate;return((t==null?void 0:t.clearInterval)||clearInterval)(e)},delegate:void 0};var ft=function(e){z(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o.pending=!1,o}return t.prototype.schedule=function(r,n){if(n===void 0&&(n=0),this.closed)return this;this.state=r;var o=this.id,i=this.scheduler;return o!=null&&(this.id=this.recycleAsyncId(i,o,n)),this.pending=!0,this.delay=n,this.id=this.id||this.requestAsyncId(i,this.id,n),this},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),Ge.setInterval(r.flush.bind(r,this),o)},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&this.delay===o&&this.pending===!1)return n;Ge.clearInterval(n)},t.prototype.execute=function(r,n){if(this.closed)return new Error("executing a cancelled action");this.pending=!1;var o=this._execute(r,n);if(o)return o;this.pending===!1&&this.id!=null&&(this.id=this.recycleAsyncId(this.scheduler,this.id,null))},t.prototype._execute=function(r,n){var o=!1,i;try{this.work(r)}catch(a){o=!0,i=!!a&&a||new Error(a)}if(o)return this.unsubscribe(),i},t.prototype.unsubscribe=function(){if(!this.closed){var r=this,n=r.id,o=r.scheduler,i=o.actions;this.work=this.state=this.scheduler=null,this.pending=!1,ve(i,this),n!=null&&(this.id=this.recycleAsyncId(o,n,null)),this.delay=null,e.prototype.unsubscribe.call(this)}},t}(Tr);var Ut=function(){function e(t,r){r===void 0&&(r=e.now),this.schedulerActionCtor=t,this.now=r}return e.prototype.schedule=function(t,r,n){return r===void 0&&(r=0),new this.schedulerActionCtor(this,t).schedule(n,r)},e.now=Be.now,e}();var pt=function(e){z(t,e);function t(r,n){n===void 0&&(n=Ut.now);var o=e.call(this,r,n)||this;return o.actions=[],o.active=!1,o.scheduled=void 0,o}return t.prototype.flush=function(r){var n=this.actions;if(this.active){n.push(r);return}var o;this.active=!0;do if(o=r.execute(r.state,r.delay))break;while(r=n.shift());if(this.active=!1,o){for(;r=n.shift();)r.unsubscribe();throw o}},t}(Ut);var Ze=new pt(ft),Mr=Ze;var Ar=function(e){z(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r.scheduled||(r.scheduled=$e.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.length===0&&($e.cancelAnimationFrame(n),r.scheduled=void 0)},t}(ft);var Lr=function(e){z(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this.active=!0,this.scheduled=void 0;var n=this.actions,o,i=-1;r=r||n.shift();var a=n.length;do if(o=r.execute(r.state,r.delay))break;while(++i<a&&(r=n.shift()));if(this.active=!1,o){for(;++i<a&&(r=n.shift());)r.unsubscribe();throw o}},t}(pt);var J=new Lr(Ar);var me=new O(function(e){return e.complete()});function Pe(e,t){return new O(function(r){var n=0;return t.schedule(function(){n===e.length?r.complete():(r.next(e[n++]),r.closed||this.schedule())})})}var Ve=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function mt(e){return g(e==null?void 0:e.then)}function ni(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var De=ni();function _r(e,t){return new O(function(r){var n=new te;return n.add(t.schedule(function(){var o=e[Se]();n.add(o.subscribe({next:function(i){n.add(t.schedule(function(){return r.next(i)}))},error:function(i){n.add(t.schedule(function(){return r.error(i)}))},complete:function(){n.add(t.schedule(function(){return r.complete()}))}}))})),n})}function Hr(e,t){return new O(function(r){return t.schedule(function(){return e.then(function(n){r.add(t.schedule(function(){r.next(n),r.add(t.schedule(function(){return r.complete()}))}))},function(n){r.add(t.schedule(function(){return r.error(n)}))})})})}function Cr(e,t,r,n){n===void 0&&(n=0);var o=t.schedule(function(){try{r.call(this)}catch(i){e.error(i)}},n);return e.add(o),o}function jr(e,t){return new O(function(r){var n;return r.add(t.schedule(function(){n=e[De](),Cr(r,t,function(){var o=n.next(),i=o.value,a=o.done;a?r.complete():(r.next(i),this.schedule())})})),function(){return g(n==null?void 0:n.return)&&n.return()}})}function dt(e){return g(e[Se])}function ht(e){return g(e==null?void 0:e[De])}function kr(e,t){if(!e)throw new Error("Iterable cannot be null");return new O(function(r){var n=new te;return n.add(t.schedule(function(){var o=e[Symbol.asyncIterator]();n.add(t.schedule(function(){var i=this;o.next().then(function(a){a.done?r.complete():(r.next(a.value),i.schedule())})}))})),n})}function bt(e){return Symbol.asyncIterator&&g(e==null?void 0:e[Symbol.asyncIterator])}function vt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, Array, AsyncIterable, or Iterable.")}function Fr(e,t){if(e!=null){if(dt(e))return _r(e,t);if(Ve(e))return Pe(e,t);if(mt(e))return Hr(e,t);if(bt(e))return kr(e,t);if(ht(e))return jr(e,t)}throw vt(e)}function ge(e,t){return t?Fr(e,t):V(e)}function V(e){if(e instanceof O)return e;if(e!=null){if(dt(e))return oi(e);if(Ve(e))return Wt(e);if(mt(e))return ii(e);if(bt(e))return si(e);if(ht(e))return ai(e)}throw vt(e)}function oi(e){return new O(function(t){var r=e[Se]();if(g(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Wt(e){return new O(function(t){for(var r=0;r<e.length&&!t.closed;r++)t.next(e[r]);t.complete()})}function ii(e){return new O(function(t){e.then(function(r){t.closed||(t.next(r),t.complete())},function(r){return t.error(r)}).then(null,ut)})}function ai(e){return new O(function(t){for(var r=e[De]();!t.closed;){var n=r.next(),o=n.done,i=n.value;o?t.complete():t.next(i)}return function(){return g(r==null?void 0:r.return)&&r.return()}})}function si(e){return new O(function(t){ci(e,t).catch(function(r){return t.error(r)})})}function ci(e,t){var r,n,o,i;return mr(this,void 0,void 0,function(){var a,c;return dr(this,function(u){switch(u.label){case 0:u.trys.push([0,5,6,11]),r=hr(e),u.label=1;case 1:return[4,r.next()];case 2:if(n=u.sent(),!!n.done)return[3,4];a=n.value,t.next(a),u.label=3;case 3:return[3,1];case 4:return[3,11];case 5:return c=u.sent(),o={error:c},[3,11];case 6:return u.trys.push([6,,9,10]),n&&!n.done&&(i=r.return)?[4,i.call(r)]:[3,8];case 7:u.sent(),u.label=8;case 8:return[3,10];case 9:if(o)throw o.error;return[7];case 10:return[7];case 11:return t.complete(),[2]}})})}function de(e,t){return t?Pe(e,t):Wt(e)}function gt(e){return e&&g(e.schedule)}function Nt(e){return e[e.length-1]}function we(e){return g(Nt(e))?e.pop():void 0}function ue(e){return gt(Nt(e))?e.pop():void 0}function yt(e,t){return typeof Nt(e)=="number"?e.pop():t}function H(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e);return r?Pe(e,r):de(e)}function Ir(e){return e instanceof Date&&!isNaN(e)}function f(e,t){return m(function(r,n){var o=0;r.subscribe(new b(n,function(i){n.next(e.call(t,i,o++))}))})}var ui=Array.isArray;function li(e,t){return ui(t)?e.apply(void 0,I([],C(t))):e(t)}function Ue(e){return f(function(t){return li(e,t)})}function X(e,t){return t===void 0&&(t=0),m(function(r,n){r.subscribe(new b(n,function(o){return n.add(e.schedule(function(){return n.next(o)},t))},function(o){return n.add(e.schedule(function(){return n.error(o)},t))},function(){return n.add(e.schedule(function(){return n.complete()},t))}))})}var fi=Array.isArray,pi=Object.getPrototypeOf,mi=Object.prototype,di=Object.keys;function Rr(e){if(e.length===1){var t=e[0];if(fi(t))return{args:t,keys:null};if(hi(t)){var r=di(t);return{args:r.map(function(n){return t[n]}),keys:r}}}return{args:e,keys:null}}function hi(e){return e&&typeof e=="object"&&pi(e)===mi}function $r(e,t){return e.reduce(function(r,n,o){return r[n]=t[o],r},{})}function B(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=we(e),o=Rr(e),i=o.args,a=o.keys;if(i.length===0)return ge([],r);var c=new O(zt(i,r,a?function(u){return $r(a,u)}:se));return n?c.pipe(Ue(n)):c}function zt(e,t,r){return r===void 0&&(r=se),function(n){Pr(t,function(){for(var o=e.length,i=new Array(o),a=o,c=o,u=function(l){Pr(t,function(){var p=ge(e[l],t),d=!1;p.subscribe(new b(n,function(_){i[l]=_,d||(d=!0,c--),c||n.next(r(i.slice()))},void 0,function(){--a||n.complete()}))},n)},s=0;s<o;s++)u(s)},n)}}function Pr(e,t,r){e?r.add(e.schedule(t)):t()}function Vr(e,t,r,n,o,i,a,c){var u=[],s=0,l=0,p=!1,d=function(){p&&!u.length&&!s&&t.complete()},_=function(A){return s<n?$(A):u.push(A)},$=function(A){i&&t.next(A),s++;var Z=!1;V(r(A,l++)).subscribe(new b(t,function(P){o==null||o(P),i?_(P):t.next(P)},void 0,function(){Z=!0},function(){if(Z)try{s--;for(var P=function(){var F=u.shift();a?t.add(a.schedule(function(){return $(F)})):$(F)};u.length&&s<n;)P();d()}catch(F){t.error(F)}}))};return e.subscribe(new b(t,_,void 0,function(){p=!0,d()})),function(){c==null||c()}}function re(e,t,r){return r===void 0&&(r=Infinity),g(t)?re(function(n,o){return f(function(i,a){return t(n,i,o,a)})(V(e(n,o)))},r):(typeof t=="number"&&(r=t),m(function(n,o){return Vr(n,o,e,r)}))}function We(e){return e===void 0&&(e=Infinity),re(se,e)}function Dr(){return We(1)}function et(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Dr()(de(e,ue(e)))}function Ee(e){return new O(function(t){V(e()).subscribe(t)})}var bi=["addListener","removeListener"],vi=["addEventListener","removeEventListener"],gi=["on","off"];function w(e,t,r,n){if(g(r)&&(n=r,r=void 0),n)return w(e,t,r).pipe(Ue(n));var o=C(Si(e)?vi.map(function(c){return function(u){return e[c](t,u,r)}}):yi(e)?bi.map(Ur(e,t)):xi(e)?gi.map(Ur(e,t)):[],2),i=o[0],a=o[1];return!i&&Ve(e)?re(function(c){return w(c,t,r)})(de(e)):new O(function(c){if(!i)throw new TypeError("Invalid event target");var u=function(){for(var s=[],l=0;l<arguments.length;l++)s[l]=arguments[l];return c.next(1<s.length?s:s[0])};return i(u),function(){return a(u)}})}function Ur(e,t){return function(r){return function(n){return e[r](t,n)}}}function yi(e){return g(e.addListener)&&g(e.removeListener)}function xi(e){return g(e.on)&&g(e.off)}function Si(e){return g(e.addEventListener)&&g(e.removeEventListener)}function Wr(e,t,r){e===void 0&&(e=0),r===void 0&&(r=Mr);var n=-1;return t!=null&&(gt(t)?r=t:n=t),new O(function(o){var i=Ir(e)?+e-r.now():e;i<0&&(i=0);var a=0;return r.schedule(function(){o.closed||(o.next(a++),0<=n?this.schedule(void 0,n):o.complete())},i)})}var wi=Array.isArray;function Oe(e){return e.length===1&&wi(e[0])?e[0]:e}function j(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=yt(e,Infinity),o=Oe(e);return o.length?o.length===1?V(o[0]):We(n)(de(o,r)):me}var G=new O(Y);function M(e,t){return m(function(r,n){var o=0;r.subscribe(new b(n,function(i){return e.call(t,i,o++)&&n.next(i)}))})}function Nr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e),n=Oe(e);return n.length?new O(function(o){var i=n.map(function(){return[]}),a=n.map(function(){return!1});o.add(function(){i=a=null});for(var c=function(s){V(n[s]).subscribe(new b(o,function(l){if(i[s].push(l),i.every(function(d){return d.length})){var p=i.map(function(d){return d.shift()});o.next(r?r.apply(void 0,I([],C(p))):p),i.some(function(d,_){return!d.length&&a[_]})&&o.complete()}},void 0,function(){a[s]=!0,!i[s].length&&o.complete()}))},u=0;!o.closed&&u<n.length;u++)c(u);return function(){i=a=null}}):me}function Te(e,t){return t===void 0&&(t=null),t=t!=null?t:e,m(function(r,n){var o=[],i=0;r.subscribe(new b(n,function(a){var c,u,s,l,p=null;i++%t==0&&o.push([]);try{for(var d=ae(o),_=d.next();!_.done;_=d.next()){var $=_.value;$.push(a),e<=$.length&&(p=p!=null?p:[],p.push($))}}catch(P){c={error:P}}finally{try{_&&!_.done&&(u=d.return)&&u.call(d)}finally{if(c)throw c.error}}if(p)try{for(var A=ae(p),Z=A.next();!Z.done;Z=A.next()){var $=Z.value;ve(o,$),n.next($)}}catch(P){s={error:P}}finally{try{Z&&!Z.done&&(l=A.return)&&l.call(A)}finally{if(s)throw s.error}}},void 0,function(){var a,c;try{for(var u=ae(o),s=u.next();!s.done;s=u.next()){var l=s.value;n.next(l)}}catch(p){a={error:p}}finally{try{s&&!s.done&&(c=u.return)&&c.call(u)}finally{if(a)throw a.error}}n.complete()},function(){o=null}))})}function tt(e){return m(function(t,r){var n=null,o=!1,i;n=t.subscribe(new b(r,void 0,function(a){i=V(e(a,tt(e)(t))),n?(n.unsubscribe(),n=null,i.subscribe(r)):o=!0})),o&&(n.unsubscribe(),n=null,i.subscribe(r))})}function zr(e,t,r,n,o){return function(i,a){var c=r,u=t,s=0;i.subscribe(new b(a,function(l){var p=s++;u=c?e(u,l,p):(c=!0,l),n&&a.next(u)},void 0,o&&function(){c&&a.next(u),a.complete()}))}}function Qr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e);return r?Sr(Qr.apply(void 0,I([],C(e))),Ue(r)):m(function(n,o){zt(I([n],C(Oe(e))))(o)})}function Qt(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Qr.apply(void 0,I([],C(e)))}function qr(e,t){return g(t)?re(e,t,1):re(e,1)}function Kr(e,t){return t===void 0&&(t=Ze),m(function(r,n){var o=null,i=null,a=null,c=function(){if(o){o.unsubscribe(),o=null;var s=i;i=null,n.next(s)}};function u(){var s=a+e,l=t.now();if(l<s){o=this.schedule(void 0,s-l);return}c()}r.subscribe(new b(n,function(s){i=s,a=t.now(),o||(o=t.schedule(u,e))},void 0,function(){c(),n.complete()},function(){i=o=null}))})}function xt(e){return e===void 0&&(e=null),m(function(t,r){var n=!1;t.subscribe(new b(r,function(o){n=!0,r.next(o)},void 0,function(){n||r.next(e),r.complete()}))})}function rt(e){return e<=0?function(){return me}:m(function(t,r){var n=0;t.subscribe(new b(r,function(o){++n<=e&&(r.next(o),e<=n&&r.complete())}))})}function Yr(){return m(function(e,t){e.subscribe(new b(t,Y))})}function ce(e){return m(function(t,r){t.subscribe(new b(r,function(){return r.next(e)}))})}function qt(e,t){return t?function(r){return et(t.pipe(rt(1),Yr()),r.pipe(qt(e)))}:re(function(r,n){return e(r,n).pipe(rt(1),ce(r))})}function Me(e,t){t===void 0&&(t=Ze);var r=Wr(e,t);return qt(function(){return r})}function Q(e,t){return t===void 0&&(t=se),e=e!=null?e:Ei,m(function(r,n){var o,i=!0;r.subscribe(new b(n,function(a){var c=t(a);(i||!e(o,c))&&(i=!1,o=c,n.next(a))}))})}function Ei(e,t){return e===t}function W(e,t){return Q(function(r,n){return t?t(r[e],n[e]):r[e]===n[e]})}function D(e){return m(function(t,r){t.subscribe(r),r.add(e)})}function Jr(e){return e<=0?function(){return me}:m(function(t,r){var n=[];t.subscribe(new b(r,function(o){n.push(o),e<n.length&&n.shift()},void 0,function(){var o,i;try{for(var a=ae(n),c=a.next();!c.done;c=a.next()){var u=c.value;r.next(u)}}catch(s){o={error:s}}finally{try{c&&!c.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}r.complete()},function(){n=null}))})}function Oi(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e),n=yt(e,Infinity);return e=Oe(e),m(function(o,i){We(n)(de(I([o],C(e)),r)).subscribe(i)})}function St(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Oi.apply(void 0,I([],C(e)))}function nt(e){return m(function(t,r){var n=!1,o=null;t.subscribe(new b(r,function(a){n=!0,o=a}));var i=function(){if(n){n=!1;var a=o;o=null,r.next(a)}};e.subscribe(new b(r,i,void 0,Y))})}function Xr(e,t){return m(zr(e,t,arguments.length>=2,!0))}function ne(e){e=e||{};var t=e.connector,r=t===void 0?function(){return new T}:t,n=e.resetOnComplete,o=n===void 0?!0:n,i=e.resetOnError,a=i===void 0?!0:i,c=e.resetOnRefCountZero,u=c===void 0?!0:c,s=null,l=null,p=0,d=!1,_=!1,$=function(){s=l=null,d=_=!1};return m(function(A,Z){return p++,l=l!=null?l:r(),l.subscribe(Z),s||(s=ge(A).subscribe({next:function(P){return l.next(P)},error:function(P){_=!0;var F=l;a&&$(),F.error(P)},complete:function(){d=!0;var P=l;o&&$(),P.complete()}})),function(){if(p--,u&&!p&&!_&&!d){var P=s;$(),P==null||P.unsubscribe()}}})}function oe(e,t,r){var n,o,i,a=!1;return e&&typeof e=="object"?(i=(n=e.bufferSize)!==null&&n!==void 0?n:Infinity,t=(o=e.windowTime)!==null&&o!==void 0?o:Infinity,a=!!e.refCount,r=e.scheduler):i=e!=null?e:Infinity,ne({connector:function(){return new lt(i,t,r)},resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:a})}function Kt(e){return M(function(t,r){return e<=r})}function Br(e){return m(function(t,r){var n=!1,o=new b(r,function(){o==null||o.unsubscribe(),n=!0},void 0,Y);V(e).subscribe(o),t.subscribe(new b(r,function(i){return n&&r.next(i)}))})}function U(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=ue(e);return m(function(n,o){(r?et(e,n,r):et(e,n)).subscribe(o)})}function x(e,t){return m(function(r,n){var o=null,i=0,a=!1,c=function(){return a&&!o&&n.complete()};r.subscribe(new b(n,function(u){o==null||o.unsubscribe();var s=0,l=i++;V(e(u,l)).subscribe(o=new b(n,function(p){return n.next(t?t(u,p,l,s++):p)},void 0,function(){o=null,c()}))},void 0,function(){a=!0,c()}))})}function Gr(e,t){return t?x(function(){return e},t):x(function(){return e})}function Zr(e){return m(function(t,r){V(e).subscribe(new b(r,function(){return r.complete()},void 0,Y)),!r.closed&&t.subscribe(r)})}function en(e,t){return t===void 0&&(t=!1),m(function(r,n){var o=0;r.subscribe(new b(n,function(i){var a=e(i,o++);(a||t)&&n.next(i),!a&&n.complete()}))})}function k(e,t,r){var n=g(e)||t||r?{next:e,error:t,complete:r}:e;return n?m(function(o,i){o.subscribe(new b(i,function(a){var c;(c=n.next)===null||c===void 0||c.call(n,a),i.next(a)},function(a){var c;(c=n.error)===null||c===void 0||c.call(n,a),i.error(a)},function(){var a;(a=n.complete)===null||a===void 0||a.call(n),i.complete()}))}):se}var Ti={leading:!0,trailing:!1};function tn(e,t){var r=t===void 0?Ti:t,n=r.leading,o=r.trailing;return m(function(i,a){var c=!1,u=null,s=null,l=!1,p=function(){s==null||s.unsubscribe(),s=null,o&&($(),l&&a.complete())},d=function(){s=null,l&&a.complete()},_=function(A){return s=V(e(A)).subscribe(new b(a,p,void 0,d))},$=function(){c&&(a.next(u),!l&&_(u)),c=!1,u=null};i.subscribe(new b(a,function(A){c=!0,u=A,!(s&&!s.closed)&&(n?$():_(A))},void 0,function(){l=!0,!(o&&c&&s&&!s.closed)&&a.complete()}))})}function he(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=we(e);return m(function(n,o){for(var i=e.length,a=new Array(i),c=e.map(function(){return!1}),u=!1,s=function(p){V(e[p]).subscribe(new b(o,function(d){a[p]=d,!u&&!c[p]&&(c[p]=!0,(u=c.every(se))&&(c=null))},void 0,Y))},l=0;l<i;l++)s(l);n.subscribe(new b(o,function(p){if(u){var d=I([p],C(a));o.next(r?r.apply(void 0,I([],C(d))):d)}}))})}function Mi(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return m(function(r,n){Nr.apply(void 0,I([r],C(e))).subscribe(n)})}function rn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Mi.apply(void 0,I([],C(e)))}function nn(){let e=new lt;return w(document,"DOMContentLoaded").pipe(ce(document)).subscribe(e),e}function ie(e,t=document){return t.querySelector(e)||void 0}function le(e,t=document){let r=ie(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function He(){return document.activeElement instanceof HTMLElement?document.activeElement:void 0}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function Ne(e){return document.createElement(e)}function Ce(e,...t){e.replaceWith(...t)}function Ae(e,t=!0){t?e.focus():e.blur()}function on(e){return j(w(e,"focus"),w(e,"blur")).pipe(f(({type:t})=>t==="focus"),U(e===He()))}var an=new T,Ai=Ee(()=>H(new ResizeObserver(e=>{for(let t of e)an.next(t)}))).pipe(x(e=>G.pipe(U(e)).pipe(D(()=>e.disconnect()))),oe(1));function je(e){return{width:e.offsetWidth,height:e.offsetHeight}}function wt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ze(e){return Ai.pipe(k(t=>t.observe(e)),x(t=>an.pipe(M(({target:r})=>r===e),D(()=>t.unobserve(e)),f(({contentRect:r})=>({width:r.width,height:r.height})))),U(je(e)))}function sn(e){return{x:e.scrollLeft,y:e.scrollTop}}function Li(e){return j(w(e,"scroll"),w(window,"resize")).pipe(f(()=>sn(e)),U(sn(e)))}function cn(e,t=16){return Li(e).pipe(f(({y:r})=>{let n=je(e),o=wt(e);return r>=o.height-n.height-t}),Q())}function un(e){if(e instanceof HTMLInputElement)e.select();else throw new Error("Not implemented")}var Et={drawer:le("[data-md-toggle=drawer]"),search:le("[data-md-toggle=search]")};function ln(e){return Et[e].checked}function ke(e,t){Et[e].checked!==t&&Et[e].click()}function Ot(e){let t=Et[e];return w(t,"change").pipe(f(()=>t.checked),U(t.checked))}function _i(e){switch(e.tagName){case"INPUT":case"SELECT":case"TEXTAREA":return!0;default:return e.isContentEditable}}function fn(){return w(window,"keydown").pipe(M(e=>!(e.metaKey||e.ctrlKey)),f(e=>({mode:ln("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),M(({mode:e})=>{if(e==="global"){let t=He();if(typeof t!="undefined")return!_i(t)}return!0}),ne())}function pn(){return new URL(location.href)}function mn(e){location.href=e.href}function dn(){return new T}function hn(){return location.hash.substring(1)}function bn(e){let t=Ne("a");t.href=e,t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Hi(){return w(window,"hashchange").pipe(f(hn),U(hn()),M(e=>e.length>0),ne())}function vn(){return Hi().pipe(x(e=>H(ie(`[id="${e}"]`))))}function Qe(e){let t=matchMedia(e);return w(t,"change").pipe(f(r=>r.matches),U(t.matches))}function gn(){return j(Qe("print").pipe(M(Boolean)),w(window,"beforeprint")).pipe(ce(void 0))}function Yt(e,t){return e.pipe(x(r=>r?t():G))}function Tt(e,t={credentials:"same-origin"}){return ge(fetch(e.toString(),t)).pipe(M(r=>r.status===200))}function Le(e,t){return Tt(e,t).pipe(x(r=>r.json()),oe(1))}function yn(e,t){let r=new DOMParser;return Tt(e,t).pipe(x(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),oe(1))}function xn(){return{x:Math.max(0,pageXOffset),y:Math.max(0,pageYOffset)}}function Jt({x:e,y:t}){window.scrollTo(e||0,t||0)}function Sn(){return j(w(window,"scroll",{passive:!0}),w(window,"resize",{passive:!0})).pipe(f(xn),U(xn()))}function wn(){return{width:innerWidth,height:innerHeight}}function En(){return w(window,"resize",{passive:!0}).pipe(f(wn),U(wn()))}function On(){return B([Sn(),En()]).pipe(f(([e,t])=>({offset:e,size:t})),oe(1))}function Mt(e,{viewport$:t,header$:r}){let n=t.pipe(W("size")),o=B([n,r]).pipe(f(()=>({x:e.offsetLeft,y:e.offsetTop})));return B([r,t,o]).pipe(f(([{height:i},{offset:a,size:c},{x:u,y:s}])=>({offset:{x:a.x-u,y:a.y-s+i},size:c})))}function Tn(e,{tx$:t}){let r=w(e,"message").pipe(f(({data:n})=>n));return t.pipe(tn(()=>r,{leading:!0,trailing:!0}),k(n=>e.postMessage(n)),Gr(r),ne())}var Ci=le("#__config"),qe=JSON.parse(Ci.textContent);qe.base=new URL(qe.base,pn()).toString().replace(/\/$/,"");function ee(){return qe}function At(e){return qe.features.includes(e)}function K(e,t){return typeof t!="undefined"?qe.translations[e].replace("#",t.toString()):qe.translations[e]}function _e(e,t=document){return le(`[data-md-component=${e}]`,t)}function be(e,t=document){return q(`[data-md-component=${e}]`,t)}var so=it(Bt());function Mn(e,t=0){e.setAttribute("tabindex",t.toString())}function An(e){e.removeAttribute("tabindex")}function Ln(e,t){e.setAttribute("data-md-state","lock"),e.style.top=`-${t}px`}function _n(e){let t=-1*parseInt(e.style.top,10);e.removeAttribute("data-md-state"),e.style.top="",t&&window.scrollTo(0,t)}function Hn(e,t){e.setAttribute("data-md-state",t)}function Cn(e){e.removeAttribute("data-md-state")}function jn(e,t){e.classList.toggle("md-nav__link--active",t)}function kn(e){e.classList.remove("md-nav__link--active")}function Fn(e,t){e.firstElementChild.innerHTML=t}function In(e,t){e.setAttribute("data-md-state",t)}function Rn(e){e.removeAttribute("data-md-state")}function $n(e,t){e.setAttribute("data-md-state",t)}function Pn(e){e.removeAttribute("data-md-state")}function Vn(e,t){e.setAttribute("data-md-state",t)}function Dn(e){e.removeAttribute("data-md-state")}function Un(e,t){e.placeholder=t}function Wn(e){e.placeholder=K("search.placeholder")}function Nn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Nn(e,r)}function R(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="boolean"?n.setAttribute(o,t[o]):t[o]&&n.setAttribute(o,"");for(let o of r)Nn(n,o);return n}function zn(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function ye(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ji(e){let t=0;for(let r=0,n=e.length;r<n;r++)t=(t<<5)-t+e.charCodeAt(r),t|=0;return t}function Gt(e){let t=ee();return`${e}[${ji(t.base)}]`}function Qn(e,t){switch(t){case 0:e.textContent=K("search.result.none");break;case 1:e.textContent=K("search.result.one");break;default:e.textContent=K("search.result.other",ye(t))}}function qn(e){e.textContent=K("search.result.placeholder")}function Kn(e,t){e.appendChild(t)}function Yn(e){e.innerHTML=""}function Jn(e,t){e.style.top=`${t}px`}function Xn(e){e.style.top=""}function Bn(e,t){let r=e.firstElementChild;r.style.height=`${t-2*r.offsetTop}px`}function Gn(e){let t=e.firstElementChild;t.style.height=""}function Zn(e,t){e.lastElementChild.appendChild(t)}function eo(e,t){e.lastElementChild.setAttribute("data-md-state",t)}function to(e,t){e.setAttribute("data-md-state",t)}function Zt(e){e.removeAttribute("data-md-state")}function ro(e){return R("button",{class:"md-clipboard md-icon",title:K("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Fe;(function(e){e[e.TEASER=1]="TEASER",e[e.PARENT=2]="PARENT"})(Fe||(Fe={}));function er(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).map(a=>[R("del",null,a)," "]).flat().slice(0,-1),i=e.location;return R("a",{href:i,class:"md-search-result__link",tabIndex:-1},R("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&R("div",{class:"md-search-result__icon md-icon"}),R("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&R("p",{class:"md-search-result__teaser"},zn(e.text,320)),n>0&&o.length>0&&R("p",{class:"md-search-result__terms"},K("search.result.term.missing"),": ",o)))}function no(e){let t=e[0].score,r=[...e],n=r.findIndex(s=>!s.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(s=>s.score<t);i===-1&&(i=r.length);let a=r.slice(0,i),c=r.slice(i),u=[er(o,2|+(!n&&i===0)),...a.map(s=>er(s,1)),...c.length?[R("details",{class:"md-search-result__more"},R("summary",{tabIndex:-1},c.length>0&&c.length===1?K("search.result.more.one"):K("search.result.more.other",c.length)),c.map(s=>er(s,1)))]:[]];return R("li",{class:"md-search-result__item"},u)}function oo(e){return R("ul",{class:"md-source__facts"},e.map(t=>R("li",{class:"md-source__fact"},t)))}function io(e){return R("div",{class:"md-typeset__scrollwrap"},R("div",{class:"md-typeset__table"},e))}function ao(e){let t=ee(),[,r]=t.base.match(/([^/]+)\/?$/),n=e.find(({version:o,aliases:i})=>o===r||i.includes(r))||e[0];return R("div",{class:"md-version"},R("span",{class:"md-version__current"},n.version),R("ul",{class:"md-version__list"},e.map(o=>R("li",{class:"md-version__item"},R("a",{class:"md-version__link",href:`${new URL(o.version,t.base)}`},o.title)))))}var ki=0;function Fi(e,{viewport$:t}){let r=H(e).pipe(x(n=>{let o=n.closest("[data-tabs]");return o instanceof HTMLElement?j(...q("input",o).map(i=>w(i,"change"))):G}));return j(t.pipe(W("size")),r).pipe(f(()=>{let n=je(e);return{scroll:wt(e).width>n.width}}),W("scroll"))}function co(e,t){let r=new T;if(r.pipe(he(Qe("(hover)"))).subscribe(([{scroll:n},o])=>{n&&o?Mn(e):An(e)}),so.default.isSupported()){let n=e.closest("pre");n.id=`__code_${ki++}`,n.insertBefore(ro(n.id),e)}return Fi(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function Ii(e,{target$:t,print$:r}){return t.pipe(f(n=>n.closest("details:not([open])")),M(n=>e===n),St(r),ce(e))}function uo(e,t){let r=new T;return r.subscribe(()=>{e.setAttribute("open",""),e.scrollIntoView()}),Ii(e,t).pipe(k(r),D(()=>r.complete()),ce({ref:e}))}var lo=Ne("table");function fo(e){return Ce(e,lo),Ce(lo,io(e)),H({ref:e})}function po(e,{target$:t,viewport$:r,print$:n}){return j(...q("pre > code",e).map(o=>co(o,{viewport$:r})),...q("table:not([class])",e).map(o=>fo(o)),...q("details",e).map(o=>uo(o,{target$:t,print$:n})))}function Ri(e,{alert$:t}){return t.pipe(x(r=>j(H(!0),H(!1).pipe(Me(2e3))).pipe(f(n=>({message:r,open:n})))))}function mo(e,t){let r=new T;return r.pipe(X(J)).subscribe(({message:n,open:o})=>{Fn(e,n),o?In(e,"open"):Rn(e)}),Ri(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function $i({viewport$:e}){if(!At("header.autohide"))return H(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Te(2,1),f(([o,i])=>[o<i,i]),W(0)),r=B([e,t]).pipe(M(([{offset:o},[,i]])=>Math.abs(i-o.y)>100),f(([,[o]])=>o),Q()),n=Ot("search");return B([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),Q(),x(o=>o?r:H(!1)),U(!1))}function ho(e,t){return Ee(()=>{let r=getComputedStyle(e);return H(r.position==="sticky"||r.position==="-webkit-sticky")}).pipe(Qt(ze(e),$i(t)),f(([r,{height:n},o])=>({height:r?n:0,sticky:r,hidden:o})),Q((r,n)=>r.sticky===n.sticky&&r.height===n.height&&r.hidden===n.hidden),oe(1))}function bo(e,{header$:t,main$:r}){let n=new T;return n.pipe(W("active"),Qt(t),X(J)).subscribe(([{active:o},{hidden:i}])=>{o?$n(e,i?"hidden":"shadow"):Pn(e)}),r.subscribe(o=>n.next(o)),t.pipe(f(o=>N({ref:e},o)))}function Pi(e,{viewport$:t,header$:r}){return Mt(e,{header$:r,viewport$:t}).pipe(f(({offset:{y:n}})=>{let{height:o}=je(e);return{active:n>=o}}),W("active"))}function vo(e,t){let r=new T;r.pipe(X(J)).subscribe(({active:o})=>{o?Vn(e,"active"):Dn(e)});let n=ie("article h1");return typeof n=="undefined"?G:Pi(n,t).pipe(k(r),D(()=>r.complete()),f(o=>N({ref:e},o)))}function go(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),Q()),o=n.pipe(x(()=>ze(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),W("bottom"))));return B([n,o,t]).pipe(f(([i,{top:a,bottom:c},{offset:{y:u},size:{height:s}}])=>(s=Math.max(0,s-Math.max(0,a-u,i)-Math.max(0,s+u-c)),{offset:a-i,height:s,active:a-i<=u})),Q((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}var tr=it(Bt());function yo({alert$:e}){tr.default.isSupported()&&new O(t=>{new tr.default("[data-clipboard-target], [data-clipboard-text]").on("success",r=>t.next(r))}).subscribe(()=>e.next(K("clipboard.copied")))}function Vi(e){if(e.length<2)return e;let[t,r]=e.sort((i,a)=>i.length-a.length).map(i=>i.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;let o=ee();return e.map(i=>i.replace(t.slice(0,n),`${o.base}/`))}function xo({document$:e,location$:t,viewport$:r}){let n=ee();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",w(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=ie("link[rel='shortcut icon']");typeof o!="undefined"&&(o.href=o.href);let i=yn(`${n.base}/sitemap.xml`).pipe(f(s=>Vi(q("loc",s).map(l=>l.textContent))),x(s=>w(document.body,"click").pipe(M(l=>!l.metaKey&&!l.ctrlKey),x(l=>{if(l.target instanceof Element){let p=l.target.closest("a");if(p&&!p.target&&s.includes(p.href))return l.preventDefault(),H({url:new URL(p.href)})}return G}))),ne()),a=w(window,"popstate").pipe(M(s=>s.state!==null),f(s=>({url:new URL(location.href),offset:s.state})),ne());j(i,a).pipe(Q((s,l)=>s.url.href===l.url.href),f(({url:s})=>s)).subscribe(t);let c=t.pipe(W("pathname"),x(s=>Tt(s.href).pipe(tt(()=>(mn(s),G)))),ne());i.pipe(nt(c)).subscribe(({url:s})=>{history.pushState({},"",s.toString())});let u=new DOMParser;c.pipe(x(s=>s.text()),f(s=>u.parseFromString(s,"text/html"))).subscribe(e),j(i,a).pipe(nt(e)).subscribe(({url:s,offset:l})=>{s.hash&&!l?bn(s.hash):Jt(l||{y:0})}),e.pipe(Kt(1)).subscribe(s=>{for(let l of["title","link[rel='canonical']","meta[name='author']","meta[name='description']","[data-md-component=announce]","[data-md-component=header-topic]","[data-md-component=container]","[data-md-component=skip]"]){let p=ie(l),d=ie(l,s);typeof p!="undefined"&&typeof d!="undefined"&&Ce(p,d)}}),e.pipe(Kt(1),f(()=>_e("container")),x(s=>H(...q("script",s))),qr(s=>{let l=Ne("script");if(s.src){for(let p of s.getAttributeNames())l.setAttribute(p,s.getAttribute(p));return Ce(s,l),new O(p=>{l.onload=()=>p.complete()})}else return l.textContent=s.textContent,Ce(s,l),me})).subscribe(),r.pipe(Br(i),Kr(250),W("offset")).subscribe(({offset:s})=>{history.replaceState(s,"")}),j(i,a).pipe(Te(2,1),M(([s,l])=>s.url.pathname===l.url.pathname),f(([,s])=>s)).subscribe(({offset:s})=>{Jt(s||{y:0})})}var Wi=it(wo());function Eo(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}var xe;(function(e){e[e.SETUP=0]="SETUP",e[e.READY=1]="READY",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"})(xe||(xe={}));function Oo(e){return e.type===1}function To(e){return e.type===2}function Lt(e){return e.type===3}function Ni({config:e,docs:t,index:r}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[K("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=K("search.config.separator"));let n=K("search.config.pipeline").split(/\s*,\s*/).filter(Boolean);return{config:e,docs:t,index:r,pipeline:n}}function Mo(e,t){let r=ee(),n=new Worker(e),o=new T,i=Tn(n,{tx$:o}).pipe(f(a=>{if(Lt(a))for(let c of a.data)for(let u of c)u.location=`${r.base}/${u.location}`;return a}),ne());return ge(t).pipe(f(a=>({type:xe.SETUP,data:Ni(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function Ao(){let e=ee();Le(new URL("versions.json",e.base)).subscribe(t=>{le(".md-header__topic").appendChild(ao(t))})}function zi(e){let t=(__search==null?void 0:__search.transform)||Eo,r=on(e),n=j(w(e,"keyup"),w(e,"focus").pipe(Me(1))).pipe(f(()=>t(e.value)),Q());return B([n,r]).pipe(f(([o,i])=>({value:o,focus:i})))}function Lo(e,{tx$:t}){let r=new T;return r.pipe(W("value"),f(({value:n})=>({type:xe.QUERY,data:n}))).subscribe(t.next.bind(t)),r.pipe(W("focus")).subscribe(({focus:n})=>{n?(ke("search",n),Un(e,"")):Wn(e)}),w(e.form,"reset").pipe(Zr(r.pipe(Jr(1)))).subscribe(()=>Ae(e)),zi(e).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function _o(e,{rx$:t},{query$:r}){let n=new T,o=cn(e.parentElement).pipe(M(Boolean)),i=le(":scope > :first-child",e);n.pipe(X(J),he(r)).subscribe(([{data:u},{value:s}])=>{s?Qn(i,u.length):qn(i)});let a=le(":scope > :last-child",e);return n.pipe(X(J),k(()=>Yn(a)),x(({data:u})=>j(H(...u.slice(0,10)),H(...u.slice(10)).pipe(Te(4),rn(o),x(([s])=>H(...s)))))).subscribe(u=>{Kn(a,no(u))}),t.pipe(M(Lt),f(({data:u})=>({data:u})),U({data:[]})).pipe(k(n),D(()=>n.complete()),f(u=>N({ref:e},u)))}function Ho(e,{index$:t,keyboard$:r}){let n=ee(),o=Mo(n.search,t),i=_e("search-query",e),a=_e("search-result",e),{tx$:c,rx$:u}=o;c.pipe(M(To),nt(u.pipe(M(Oo))),rt(1)).subscribe(c.next.bind(c)),r.pipe(M(({mode:l})=>l==="search")).subscribe(l=>{let p=He();switch(l.type){case"Enter":p===i&&l.claim();break;case"Escape":case"Tab":ke("search",!1),Ae(i,!1);break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")Ae(i);else{let d=[i,...q(":not(details) > [href], summary, details[open] [href]",a)],_=Math.max(0,(Math.max(0,d.indexOf(p))+d.length+(l.type==="ArrowUp"?-1:1))%d.length);Ae(d[_])}l.claim();break;default:i!==He()&&Ae(i)}}),r.pipe(M(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":Ae(i),un(i),l.claim();break}});let s=Lo(i,o);return j(s,_o(a,o,{query$:s}))}function Qi(e,{viewport$:t,main$:r}){let n=e.parentElement.offsetTop-e.parentElement.parentElement.offsetTop;return B([r,t]).pipe(f(([{offset:o,height:i},{offset:{y:a}}])=>(i=i+Math.min(n,Math.max(0,a-o))-n,{height:i,locked:a>=o+n})),Q((o,i)=>o.height===i.height&&o.locked===i.locked))}function rr(e,n){var{header$:t}=n,r=lr(n,["header$"]);let o=new T;return o.pipe(X(J),he(t)).subscribe({next([{height:i},{height:a}]){Bn(e,i),Jn(e,a)},complete(){Xn(e),Gn(e)}}),Qi(e,r).pipe(k(o),D(()=>o.complete()),f(i=>N({ref:e},i)))}function Co(e,t){let r=typeof t!="undefined"?`https://api.github.com/repos/${e}/${t}`:`https://api.github.com/users/${e}`;return Le(r).pipe(f(n=>{if(typeof t!="undefined"){let{stargazers_count:o,forks_count:i}=n;return[`${ye(o)} Stars`,`${ye(i)} Forks`]}else{let{public_repos:o}=n;return[`${ye(o)} Repositories`]}}),xt([]))}function jo(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Le(r).pipe(f(({star_count:n,forks_count:o})=>[`${ye(n)} Stars`,`${ye(o)} Forks`]),xt([]))}function ko(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return Co(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return jo(o,i);default:return G}}var qi;function Ki(e){return qi||(qi=Ee(()=>{let t=sessionStorage.getItem(Gt("__repo"));if(t)return H(JSON.parse(t));{let r=ko(e.href);return r.subscribe(n=>{try{sessionStorage.setItem(Gt("__repo"),JSON.stringify(n))}catch(o){}}),r}}).pipe(tt(()=>G),M(t=>t.length>0),f(t=>({facts:t})),oe(1)))}function Fo(e){let t=new T;return t.subscribe(({facts:r})=>{Zn(e,oo(r)),eo(e,"done")}),Ki(e).pipe(k(t),D(()=>t.complete()),f(r=>N({ref:e},r)))}function Yi(e,{viewport$:t,header$:r}){return Mt(e,{header$:r,viewport$:t}).pipe(f(({offset:{y:n}})=>({hidden:n>=10})),W("hidden"))}function Io(e,t){let r=new T;return r.pipe(X(J)).subscribe({next({hidden:n}){n?to(e,"hidden"):Zt(e)},complete(){Zt(e)}}),Yi(e,t).pipe(k(r),D(()=>r.complete()),f(n=>N({ref:e},n)))}function Ji(e,{viewport$:t,header$:r}){let n=new Map;for(let a of e){let c=decodeURIComponent(a.hash.substring(1)),u=ie(`[id="${c}"]`);typeof u!="undefined"&&n.set(a,u)}let o=r.pipe(f(a=>24+a.height));return ze(document.body).pipe(W("height"),f(()=>{let a=[];return[...n].reduce((c,[u,s])=>{for(;a.length&&n.get(a[a.length-1]).tagName>=s.tagName;)a.pop();let l=s.offsetTop;for(;!l&&s.parentElement;)s=s.parentElement,l=s.offsetTop;return c.set([...a=[...a,u]].reverse(),l)},new Map)}),x(a=>B([o,t]).pipe(Xr(([c,u],[s,{offset:{y:l}}])=>{for(;u.length;){let[,p]=u[0];if(p-s<l)c=[...c,u.shift()];else break}for(;c.length;){let[,p]=c[c.length-1];if(p-s>=l)u=[c.pop(),...u];else break}return[c,u]},[[],[...a]]),Q((c,u)=>c[0]===u[0]&&c[1]===u[1])))).pipe(f(([a,c])=>({prev:a.map(([u])=>u),next:c.map(([u])=>u)})),U({prev:[],next:[]}),Te(2,1),f(([a,c])=>a.prev.length<c.prev.length?{prev:c.prev.slice(Math.max(0,a.prev.length-1),c.prev.length),next:[]}:{prev:c.prev.slice(-1),next:c.next.slice(0,c.next.length-a.next.length)}))}function Ro(e,t){let r=new T;r.pipe(X(J)).subscribe(({prev:o,next:i})=>{for(let[a]of i)kn(a),Cn(a);for(let[a,[c]]of o.entries())jn(c,a===o.length-1),Hn(c,"blur")});let n=q("[href^=\\#]",e);return Ji(n,t).pipe(k(r),D(()=>r.complete()),f(o=>N({ref:e},o)))}function $o({document$:e,tablet$:t}){e.pipe(x(()=>H(...q("[data-md-state=indeterminate]"))),k(r=>{r.indeterminate=!0,r.checked=!1}),re(r=>w(r,"change").pipe(en(()=>r.hasAttribute("data-md-state")),ce(r))),he(t)).subscribe(([r,n])=>{r.removeAttribute("data-md-state"),n&&(r.checked=!1)})}function Xi(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Po({document$:e}){e.pipe(x(()=>H(...q("[data-md-scrollfix]"))),k(t=>t.removeAttribute("data-md-scrollfix")),M(Xi),re(t=>w(t,"touchstart").pipe(ce(t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Vo({viewport$:e,tablet$:t}){B([Ot("search"),t]).pipe(f(([r,n])=>r&&!n),x(r=>H(r).pipe(Me(r?400:100),X(J))),he(e)).subscribe(([r,{offset:{y:n}}])=>{r?Ln(document.body,n):_n(document.body)})}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var Ke=nn(),nr=dn(),or=vn(),ir=fn(),fe=On(),_t=Qe("(min-width: 960px)"),Do=Qe("(min-width: 1220px)"),Uo=gn(),Wo=ee(),Bi=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||Le(`${Wo.base}/search/search_index.json`):G,ar=new T;yo({alert$:ar});At("navigation.instant")&&xo({document$:Ke,location$:nr,viewport$:fe});var No;((No=Wo.version)==null?void 0:No.provider)==="mike"&&Ao();j(nr,or).pipe(Me(125)).subscribe(()=>{ke("drawer",!1),ke("search",!1)});ir.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ie("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=ie("[href][rel=next]");typeof r!="undefined"&&r.click();break}});$o({document$:Ke,tablet$:_t});Po({document$:Ke});Vo({viewport$:fe,tablet$:_t});var Ie=ho(_e("header"),{viewport$:fe}),sr=Ke.pipe(f(()=>_e("main")),x(e=>go(e,{viewport$:fe,header$:Ie})),oe(1)),Gi=j(...be("dialog").map(e=>mo(e,{alert$:ar})),...be("header").map(e=>bo(e,{viewport$:fe,header$:Ie,main$:sr})),...be("search").map(e=>Ho(e,{index$:Bi,keyboard$:ir})),...be("source").map(e=>Fo(e)),...be("tabs").map(e=>Io(e,{viewport$:fe,header$:Ie}))),Zi=Ee(()=>j(...be("content").map(e=>po(e,{target$:or,viewport$:fe,print$:Uo})),...be("header-title").map(e=>vo(e,{viewport$:fe,header$:Ie})),...be("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Yt(Do,()=>rr(e,{viewport$:fe,header$:Ie,main$:sr})):Yt(_t,()=>rr(e,{viewport$:fe,header$:Ie,main$:sr}))),...be("toc").map(e=>Ro(e,{viewport$:fe,header$:Ie})))),zo=Ke.pipe(x(()=>Zi),St(Gi),oe(1));zo.subscribe();window.document$=Ke;window.location$=nr;window.target$=or;window.keyboard$=ir;window.viewport$=fe;window.tablet$=_t;window.screen$=Do;window.print$=Uo;window.alert$=ar;window.component$=zo;})();
-/*!
- * clipboard.js v2.0.6
- * https://clipboardjs.com/
- * 
- * Licensed MIT © Zeno Rocha
- */
-/*!
- * escape-html
- * Copyright(c) 2012-2013 TJ Holowaychuk
- * Copyright(c) 2015 Andreas Lubbe
- * Copyright(c) 2015 Tiancheng "Timothy" Gu
- * MIT Licensed
- */
-/*! *****************************************************************************
-Copyright (c) Microsoft Corporation.
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THIS SOFTWARE.
-***************************************************************************** */
-//# sourceMappingURL=bundle.5cf3e710.min.js.map
-
diff --git a/latest/assets/javascripts/bundle.5cf3e710.min.js.map b/latest/assets/javascripts/bundle.5cf3e710.min.js.map
deleted file mode 100644 (file)
index 2b36e8c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "version": 3,
-  "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/util/caughtSchedule.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/fromArray.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/switchMapTo.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/size/index.ts", "src/assets/javascripts/browser/element/offset/index.ts", "src/assets/javascripts/browser/element/selection/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/index.ts", "src/assets/javascripts/actions/_/index.ts", "src/assets/javascripts/actions/anchor/index.ts", "src/assets/javascripts/actions/dialog/index.ts", "src/assets/javascripts/actions/header/_/index.ts", "src/assets/javascripts/actions/header/title/index.ts", "src/assets/javascripts/actions/search/query/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/actions/search/result/index.ts", "src/assets/javascripts/actions/sidebar/index.ts", "src/assets/javascripts/actions/source/index.ts", "src/assets/javascripts/actions/tabs/index.ts", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts"],
-  "sourcesContent": ["(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (factory());\n}(this, (function () { 'use strict';\n\n  /**\n   * Applies the :focus-visible polyfill at the given scope.\n   * A scope in this case is either the top-level Document or a Shadow Root.\n   *\n   * @param {(Document|ShadowRoot)} scope\n   * @see https://github.com/WICG/focus-visible\n   */\n  function applyFocusVisiblePolyfill(scope) {\n    var hadKeyboardEvent = true;\n    var hadFocusVisibleRecently = false;\n    var hadFocusVisibleRecentlyTimeout = null;\n\n    var inputTypesAllowlist = {\n      text: true,\n      search: true,\n      url: true,\n      tel: true,\n      email: true,\n      password: true,\n      number: true,\n      date: true,\n      month: true,\n      week: true,\n      time: true,\n      datetime: true,\n      'datetime-local': true\n    };\n\n    /**\n     * Helper function for legacy browsers and iframes which sometimes focus\n     * elements like document, body, and non-interactive SVG.\n     * @param {Element} el\n     */\n    function isValidFocusTarget(el) {\n      if (\n        el &&\n        el !== document &&\n        el.nodeName !== 'HTML' &&\n        el.nodeName !== 'BODY' &&\n        'classList' in el &&\n        'contains' in el.classList\n      ) {\n        return true;\n      }\n      return false;\n    }\n\n    /**\n     * Computes whether the given element should automatically trigger the\n     * `focus-visible` class being added, i.e. whether it should always match\n     * `:focus-visible` when focused.\n     * @param {Element} el\n     * @return {boolean}\n     */\n    function focusTriggersKeyboardModality(el) {\n      var type = el.type;\n      var tagName = el.tagName;\n\n      if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n        return true;\n      }\n\n      if (tagName === 'TEXTAREA' && !el.readOnly) {\n        return true;\n      }\n\n      if (el.isContentEditable) {\n        return true;\n      }\n\n      return false;\n    }\n\n    /**\n     * Add the `focus-visible` class to the given element if it was not added by\n     * the author.\n     * @param {Element} el\n     */\n    function addFocusVisibleClass(el) {\n      if (el.classList.contains('focus-visible')) {\n        return;\n      }\n      el.classList.add('focus-visible');\n      el.setAttribute('data-focus-visible-added', '');\n    }\n\n    /**\n     * Remove the `focus-visible` class from the given element if it was not\n     * originally added by the author.\n     * @param {Element} el\n     */\n    function removeFocusVisibleClass(el) {\n      if (!el.hasAttribute('data-focus-visible-added')) {\n        return;\n      }\n      el.classList.remove('focus-visible');\n      el.removeAttribute('data-focus-visible-added');\n    }\n\n    /**\n     * If the most recent user interaction was via the keyboard;\n     * and the key press did not include a meta, alt/option, or control key;\n     * then the modality is keyboard. Otherwise, the modality is not keyboard.\n     * Apply `focus-visible` to any current active element and keep track\n     * of our keyboard modality state with `hadKeyboardEvent`.\n     * @param {KeyboardEvent} e\n     */\n    function onKeyDown(e) {\n      if (e.metaKey || e.altKey || e.ctrlKey) {\n        return;\n      }\n\n      if (isValidFocusTarget(scope.activeElement)) {\n        addFocusVisibleClass(scope.activeElement);\n      }\n\n      hadKeyboardEvent = true;\n    }\n\n    /**\n     * If at any point a user clicks with a pointing device, ensure that we change\n     * the modality away from keyboard.\n     * This avoids the situation where a user presses a key on an already focused\n     * element, and then clicks on a different element, focusing it with a\n     * pointing device, while we still think we're in keyboard modality.\n     * @param {Event} e\n     */\n    function onPointerDown(e) {\n      hadKeyboardEvent = false;\n    }\n\n    /**\n     * On `focus`, add the `focus-visible` class to the target if:\n     * - the target received focus as a result of keyboard navigation, or\n     * - the event target is an element that will likely require interaction\n     *   via the keyboard (e.g. a text box)\n     * @param {Event} e\n     */\n    function onFocus(e) {\n      // Prevent IE from focusing the document or HTML element.\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n        addFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * On `blur`, remove the `focus-visible` class from the target.\n     * @param {Event} e\n     */\n    function onBlur(e) {\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (\n        e.target.classList.contains('focus-visible') ||\n        e.target.hasAttribute('data-focus-visible-added')\n      ) {\n        // To detect a tab/window switch, we look for a blur event followed\n        // rapidly by a visibility change.\n        // If we don't see a visibility change within 100ms, it's probably a\n        // regular focus change.\n        hadFocusVisibleRecently = true;\n        window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n        hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n          hadFocusVisibleRecently = false;\n        }, 100);\n        removeFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * If the user changes tabs, keep track of whether or not the previously\n     * focused element had .focus-visible.\n     * @param {Event} e\n     */\n    function onVisibilityChange(e) {\n      if (document.visibilityState === 'hidden') {\n        // If the tab becomes active again, the browser will handle calling focus\n        // on the element (Safari actually calls it twice).\n        // If this tab change caused a blur on an element with focus-visible,\n        // re-apply the class when the user switches back to the tab.\n        if (hadFocusVisibleRecently) {\n          hadKeyboardEvent = true;\n        }\n        addInitialPointerMoveListeners();\n      }\n    }\n\n    /**\n     * Add a group of listeners to detect usage of any pointing devices.\n     * These listeners will be added when the polyfill first loads, and anytime\n     * the window is blurred, so that they are active when the window regains\n     * focus.\n     */\n    function addInitialPointerMoveListeners() {\n      document.addEventListener('mousemove', onInitialPointerMove);\n      document.addEventListener('mousedown', onInitialPointerMove);\n      document.addEventListener('mouseup', onInitialPointerMove);\n      document.addEventListener('pointermove', onInitialPointerMove);\n      document.addEventListener('pointerdown', onInitialPointerMove);\n      document.addEventListener('pointerup', onInitialPointerMove);\n      document.addEventListener('touchmove', onInitialPointerMove);\n      document.addEventListener('touchstart', onInitialPointerMove);\n      document.addEventListener('touchend', onInitialPointerMove);\n    }\n\n    function removeInitialPointerMoveListeners() {\n      document.removeEventListener('mousemove', onInitialPointerMove);\n      document.removeEventListener('mousedown', onInitialPointerMove);\n      document.removeEventListener('mouseup', onInitialPointerMove);\n      document.removeEventListener('pointermove', onInitialPointerMove);\n      document.removeEventListener('pointerdown', onInitialPointerMove);\n      document.removeEventListener('pointerup', onInitialPointerMove);\n      document.removeEventListener('touchmove', onInitialPointerMove);\n      document.removeEventListener('touchstart', onInitialPointerMove);\n      document.removeEventListener('touchend', onInitialPointerMove);\n    }\n\n    /**\n     * When the polfyill first loads, assume the user is in keyboard modality.\n     * If any event is received from a pointing device (e.g. mouse, pointer,\n     * touch), turn off keyboard modality.\n     * This accounts for situations where focus enters the page from the URL bar.\n     * @param {Event} e\n     */\n    function onInitialPointerMove(e) {\n      // Work around a Safari quirk that fires a mousemove on <html> whenever the\n      // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n      if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n        return;\n      }\n\n      hadKeyboardEvent = false;\n      removeInitialPointerMoveListeners();\n    }\n\n    // For some kinds of state, we are interested in changes at the global scope\n    // only. For example, global pointer input, global key presses and global\n    // visibility change should affect the state at every scope:\n    document.addEventListener('keydown', onKeyDown, true);\n    document.addEventListener('mousedown', onPointerDown, true);\n    document.addEventListener('pointerdown', onPointerDown, true);\n    document.addEventListener('touchstart', onPointerDown, true);\n    document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n    addInitialPointerMoveListeners();\n\n    // For focus and blur, we specifically care about state changes in the local\n    // scope. This is because focus / blur events that originate from within a\n    // shadow root are not re-dispatched from the host element if it was already\n    // the active element in its own scope:\n    scope.addEventListener('focus', onFocus, true);\n    scope.addEventListener('blur', onBlur, true);\n\n    // We detect that a node is a ShadowRoot by ensuring that it is a\n    // DocumentFragment and also has a host property. This check covers native\n    // implementation and polyfill implementation transparently. If we only cared\n    // about the native implementation, we could just check if the scope was\n    // an instance of a ShadowRoot.\n    if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n      // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n      // have a root element to add a class to. So, we add this attribute to the\n      // host element instead:\n      scope.host.setAttribute('data-js-focus-visible', '');\n    } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n      document.documentElement.classList.add('js-focus-visible');\n      document.documentElement.setAttribute('data-js-focus-visible', '');\n    }\n  }\n\n  // It is important to wrap all references to global window and document in\n  // these checks to support server-side rendering use cases\n  // @see https://github.com/WICG/focus-visible/issues/199\n  if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n    // Make the polyfill helper globally available. This can be used as a signal\n    // to interested libraries that wish to coordinate with the polyfill for e.g.,\n    // applying the polyfill to a shadow root:\n    window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n    // Notify interested libraries of the polyfill's presence, in case the\n    // polyfill was loaded lazily:\n    var event;\n\n    try {\n      event = new CustomEvent('focus-visible-polyfill-ready');\n    } catch (error) {\n      // IE11 does not support using CustomEvent as a constructor directly:\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n    }\n\n    window.dispatchEvent(event);\n  }\n\n  if (typeof document !== 'undefined') {\n    // Apply the polyfill to the global document, so that no JavaScript\n    // coordination is required to use the polyfill in the top-level document:\n    applyFocusVisiblePolyfill(document);\n  }\n\n})));\n", "/*!\n * clipboard.js v2.0.6\n * https://clipboardjs.com/\n * \n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 6);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ (function(module, exports) {\n\nfunction select(element) {\n    var selectedText;\n\n    if (element.nodeName === 'SELECT') {\n        element.focus();\n\n        selectedText = element.value;\n    }\n    else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n        var isReadOnly = element.hasAttribute('readonly');\n\n        if (!isReadOnly) {\n            element.setAttribute('readonly', '');\n        }\n\n        element.select();\n        element.setSelectionRange(0, element.value.length);\n\n        if (!isReadOnly) {\n            element.removeAttribute('readonly');\n        }\n\n        selectedText = element.value;\n    }\n    else {\n        if (element.hasAttribute('contenteditable')) {\n            element.focus();\n        }\n\n        var selection = window.getSelection();\n        var range = document.createRange();\n\n        range.selectNodeContents(element);\n        selection.removeAllRanges();\n        selection.addRange(range);\n\n        selectedText = selection.toString();\n    }\n\n    return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n/* 1 */\n/***/ (function(module, exports) {\n\nfunction E () {\n  // Keep this empty so it's easier to inherit from\n  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n  on: function (name, callback, ctx) {\n    var e = this.e || (this.e = {});\n\n    (e[name] || (e[name] = [])).push({\n      fn: callback,\n      ctx: ctx\n    });\n\n    return this;\n  },\n\n  once: function (name, callback, ctx) {\n    var self = this;\n    function listener () {\n      self.off(name, listener);\n      callback.apply(ctx, arguments);\n    };\n\n    listener._ = callback\n    return this.on(name, listener, ctx);\n  },\n\n  emit: function (name) {\n    var data = [].slice.call(arguments, 1);\n    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n    var i = 0;\n    var len = evtArr.length;\n\n    for (i; i < len; i++) {\n      evtArr[i].fn.apply(evtArr[i].ctx, data);\n    }\n\n    return this;\n  },\n\n  off: function (name, callback) {\n    var e = this.e || (this.e = {});\n    var evts = e[name];\n    var liveEvents = [];\n\n    if (evts && callback) {\n      for (var i = 0, len = evts.length; i < len; i++) {\n        if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n          liveEvents.push(evts[i]);\n      }\n    }\n\n    // Remove event from queue to prevent memory leak\n    // Suggested by https://github.com/lazd\n    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n    (liveEvents.length)\n      ? e[name] = liveEvents\n      : delete e[name];\n\n    return this;\n  }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ }),\n/* 2 */\n/***/ (function(module, exports, __webpack_require__) {\n\nvar is = __webpack_require__(3);\nvar delegate = __webpack_require__(4);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n    if (!target && !type && !callback) {\n        throw new Error('Missing required arguments');\n    }\n\n    if (!is.string(type)) {\n        throw new TypeError('Second argument must be a String');\n    }\n\n    if (!is.fn(callback)) {\n        throw new TypeError('Third argument must be a Function');\n    }\n\n    if (is.node(target)) {\n        return listenNode(target, type, callback);\n    }\n    else if (is.nodeList(target)) {\n        return listenNodeList(target, type, callback);\n    }\n    else if (is.string(target)) {\n        return listenSelector(target, type, callback);\n    }\n    else {\n        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n    }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n    node.addEventListener(type, callback);\n\n    return {\n        destroy: function() {\n            node.removeEventListener(type, callback);\n        }\n    }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n    Array.prototype.forEach.call(nodeList, function(node) {\n        node.addEventListener(type, callback);\n    });\n\n    return {\n        destroy: function() {\n            Array.prototype.forEach.call(nodeList, function(node) {\n                node.removeEventListener(type, callback);\n            });\n        }\n    }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n    return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n/* 3 */\n/***/ (function(module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n    return value !== undefined\n        && value instanceof HTMLElement\n        && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return value !== undefined\n        && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n        && ('length' in value)\n        && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n    return typeof value === 'string'\n        || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return type === '[object Function]';\n};\n\n\n/***/ }),\n/* 4 */\n/***/ (function(module, exports, __webpack_require__) {\n\nvar closest = __webpack_require__(5);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n    var listenerFn = listener.apply(this, arguments);\n\n    element.addEventListener(type, listenerFn, useCapture);\n\n    return {\n        destroy: function() {\n            element.removeEventListener(type, listenerFn, useCapture);\n        }\n    }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n    // Handle the regular Element usage\n    if (typeof elements.addEventListener === 'function') {\n        return _delegate.apply(null, arguments);\n    }\n\n    // Handle Element-less usage, it defaults to global delegation\n    if (typeof type === 'function') {\n        // Use `document` as the first parameter, then apply arguments\n        // This is a short way to .unshift `arguments` without running into deoptimizations\n        return _delegate.bind(null, document).apply(null, arguments);\n    }\n\n    // Handle Selector-based usage\n    if (typeof elements === 'string') {\n        elements = document.querySelectorAll(elements);\n    }\n\n    // Handle Array-like based usage\n    return Array.prototype.map.call(elements, function (element) {\n        return _delegate(element, selector, type, callback, useCapture);\n    });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n    return function(e) {\n        e.delegateTarget = closest(e.target, selector);\n\n        if (e.delegateTarget) {\n            callback.call(element, e);\n        }\n    }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n/* 5 */\n/***/ (function(module, exports) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n    var proto = Element.prototype;\n\n    proto.matches = proto.matchesSelector ||\n                    proto.mozMatchesSelector ||\n                    proto.msMatchesSelector ||\n                    proto.oMatchesSelector ||\n                    proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n    while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n        if (typeof element.matches === 'function' &&\n            element.matches(selector)) {\n          return element;\n        }\n        element = element.parentNode;\n    }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n/* 6 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n__webpack_require__.r(__webpack_exports__);\n\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(0);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n\n// CONCATENATED MODULE: ./src/clipboard-action.js\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n\n\n/**\n * Inner class which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n */\n\nvar clipboard_action_ClipboardAction = function () {\n    /**\n     * @param {Object} options\n     */\n    function ClipboardAction(options) {\n        _classCallCheck(this, ClipboardAction);\n\n        this.resolveOptions(options);\n        this.initSelection();\n    }\n\n    /**\n     * Defines base properties passed from constructor.\n     * @param {Object} options\n     */\n\n\n    _createClass(ClipboardAction, [{\n        key: 'resolveOptions',\n        value: function resolveOptions() {\n            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n            this.action = options.action;\n            this.container = options.container;\n            this.emitter = options.emitter;\n            this.target = options.target;\n            this.text = options.text;\n            this.trigger = options.trigger;\n\n            this.selectedText = '';\n        }\n\n        /**\n         * Decides which selection strategy is going to be applied based\n         * on the existence of `text` and `target` properties.\n         */\n\n    }, {\n        key: 'initSelection',\n        value: function initSelection() {\n            if (this.text) {\n                this.selectFake();\n            } else if (this.target) {\n                this.selectTarget();\n            }\n        }\n\n        /**\n         * Creates a fake textarea element, sets its value from `text` property,\n         * and makes a selection on it.\n         */\n\n    }, {\n        key: 'selectFake',\n        value: function selectFake() {\n            var _this = this;\n\n            var isRTL = document.documentElement.getAttribute('dir') == 'rtl';\n\n            this.removeFake();\n\n            this.fakeHandlerCallback = function () {\n                return _this.removeFake();\n            };\n            this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;\n\n            this.fakeElem = document.createElement('textarea');\n            // Prevent zooming on iOS\n            this.fakeElem.style.fontSize = '12pt';\n            // Reset box model\n            this.fakeElem.style.border = '0';\n            this.fakeElem.style.padding = '0';\n            this.fakeElem.style.margin = '0';\n            // Move element out of screen horizontally\n            this.fakeElem.style.position = 'absolute';\n            this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';\n            // Move element to the same position vertically\n            var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n            this.fakeElem.style.top = yPosition + 'px';\n\n            this.fakeElem.setAttribute('readonly', '');\n            this.fakeElem.value = this.text;\n\n            this.container.appendChild(this.fakeElem);\n\n            this.selectedText = select_default()(this.fakeElem);\n            this.copyText();\n        }\n\n        /**\n         * Only removes the fake element after another click event, that way\n         * a user can hit `Ctrl+C` to copy because selection still exists.\n         */\n\n    }, {\n        key: 'removeFake',\n        value: function removeFake() {\n            if (this.fakeHandler) {\n                this.container.removeEventListener('click', this.fakeHandlerCallback);\n                this.fakeHandler = null;\n                this.fakeHandlerCallback = null;\n            }\n\n            if (this.fakeElem) {\n                this.container.removeChild(this.fakeElem);\n                this.fakeElem = null;\n            }\n        }\n\n        /**\n         * Selects the content from element passed on `target` property.\n         */\n\n    }, {\n        key: 'selectTarget',\n        value: function selectTarget() {\n            this.selectedText = select_default()(this.target);\n            this.copyText();\n        }\n\n        /**\n         * Executes the copy operation based on the current selection.\n         */\n\n    }, {\n        key: 'copyText',\n        value: function copyText() {\n            var succeeded = void 0;\n\n            try {\n                succeeded = document.execCommand(this.action);\n            } catch (err) {\n                succeeded = false;\n            }\n\n            this.handleResult(succeeded);\n        }\n\n        /**\n         * Fires an event based on the copy operation result.\n         * @param {Boolean} succeeded\n         */\n\n    }, {\n        key: 'handleResult',\n        value: function handleResult(succeeded) {\n            this.emitter.emit(succeeded ? 'success' : 'error', {\n                action: this.action,\n                text: this.selectedText,\n                trigger: this.trigger,\n                clearSelection: this.clearSelection.bind(this)\n            });\n        }\n\n        /**\n         * Moves focus away from `target` and back to the trigger, removes current selection.\n         */\n\n    }, {\n        key: 'clearSelection',\n        value: function clearSelection() {\n            if (this.trigger) {\n                this.trigger.focus();\n            }\n            document.activeElement.blur();\n            window.getSelection().removeAllRanges();\n        }\n\n        /**\n         * Sets the `action` to be performed which can be either 'copy' or 'cut'.\n         * @param {String} action\n         */\n\n    }, {\n        key: 'destroy',\n\n\n        /**\n         * Destroy lifecycle.\n         */\n        value: function destroy() {\n            this.removeFake();\n        }\n    }, {\n        key: 'action',\n        set: function set() {\n            var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';\n\n            this._action = action;\n\n            if (this._action !== 'copy' && this._action !== 'cut') {\n                throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n            }\n        }\n\n        /**\n         * Gets the `action` property.\n         * @return {String}\n         */\n        ,\n        get: function get() {\n            return this._action;\n        }\n\n        /**\n         * Sets the `target` property using an element\n         * that will be have its content copied.\n         * @param {Element} target\n         */\n\n    }, {\n        key: 'target',\n        set: function set(target) {\n            if (target !== undefined) {\n                if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {\n                    if (this.action === 'copy' && target.hasAttribute('disabled')) {\n                        throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n                    }\n\n                    if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n                        throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n                    }\n\n                    this._target = target;\n                } else {\n                    throw new Error('Invalid \"target\" value, use a valid Element');\n                }\n            }\n        }\n\n        /**\n         * Gets the `target` property.\n         * @return {String|HTMLElement}\n         */\n        ,\n        get: function get() {\n            return this._target;\n        }\n    }]);\n\n    return ClipboardAction;\n}();\n\n/* harmony default export */ var clipboard_action = (clipboard_action_ClipboardAction);\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(1);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(2);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n\n// CONCATENATED MODULE: ./src/clipboard.js\nvar clipboard_typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar clipboard_createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\n\n\n\n\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\nvar clipboard_Clipboard = function (_Emitter) {\n    _inherits(Clipboard, _Emitter);\n\n    /**\n     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n     * @param {Object} options\n     */\n    function Clipboard(trigger, options) {\n        clipboard_classCallCheck(this, Clipboard);\n\n        var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));\n\n        _this.resolveOptions(options);\n        _this.listenClick(trigger);\n        return _this;\n    }\n\n    /**\n     * Defines if attributes would be resolved using internal setter functions\n     * or custom functions that were passed in the constructor.\n     * @param {Object} options\n     */\n\n\n    clipboard_createClass(Clipboard, [{\n        key: 'resolveOptions',\n        value: function resolveOptions() {\n            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n            this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n            this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n            this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n            this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n        }\n\n        /**\n         * Adds a click event listener to the passed trigger.\n         * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n         */\n\n    }, {\n        key: 'listenClick',\n        value: function listenClick(trigger) {\n            var _this2 = this;\n\n            this.listener = listen_default()(trigger, 'click', function (e) {\n                return _this2.onClick(e);\n            });\n        }\n\n        /**\n         * Defines a new `ClipboardAction` on each click event.\n         * @param {Event} e\n         */\n\n    }, {\n        key: 'onClick',\n        value: function onClick(e) {\n            var trigger = e.delegateTarget || e.currentTarget;\n\n            if (this.clipboardAction) {\n                this.clipboardAction = null;\n            }\n\n            this.clipboardAction = new clipboard_action({\n                action: this.action(trigger),\n                target: this.target(trigger),\n                text: this.text(trigger),\n                container: this.container,\n                trigger: trigger,\n                emitter: this\n            });\n        }\n\n        /**\n         * Default `action` lookup function.\n         * @param {Element} trigger\n         */\n\n    }, {\n        key: 'defaultAction',\n        value: function defaultAction(trigger) {\n            return getAttributeValue('action', trigger);\n        }\n\n        /**\n         * Default `target` lookup function.\n         * @param {Element} trigger\n         */\n\n    }, {\n        key: 'defaultTarget',\n        value: function defaultTarget(trigger) {\n            var selector = getAttributeValue('target', trigger);\n\n            if (selector) {\n                return document.querySelector(selector);\n            }\n        }\n\n        /**\n         * Returns the support of the given action, or all actions if no action is\n         * given.\n         * @param {String} [action]\n         */\n\n    }, {\n        key: 'defaultText',\n\n\n        /**\n         * Default `text` lookup function.\n         * @param {Element} trigger\n         */\n        value: function defaultText(trigger) {\n            return getAttributeValue('text', trigger);\n        }\n\n        /**\n         * Destroy lifecycle.\n         */\n\n    }, {\n        key: 'destroy',\n        value: function destroy() {\n            this.listener.destroy();\n\n            if (this.clipboardAction) {\n                this.clipboardAction.destroy();\n                this.clipboardAction = null;\n            }\n        }\n    }], [{\n        key: 'isSupported',\n        value: function isSupported() {\n            var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n\n            var actions = typeof action === 'string' ? [action] : action;\n            var support = !!document.queryCommandSupported;\n\n            actions.forEach(function (action) {\n                support = support && !!document.queryCommandSupported(action);\n            });\n\n            return support;\n        }\n    }]);\n\n    return Clipboard;\n}(tiny_emitter_default.a);\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\n\nfunction getAttributeValue(suffix, element) {\n    var attribute = 'data-clipboard-' + suffix;\n\n    if (!element.hasAttribute(attribute)) {\n        return;\n    }\n\n    return element.getAttribute(attribute);\n}\n\n/* harmony default export */ var clipboard = __webpack_exports__[\"default\"] = (clipboard_Clipboard);\n\n/***/ })\n/******/ ])[\"default\"];\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\nimport { NEVER, Subject, defer, merge } from \"rxjs\"\nimport {\n  delay,\n  filter,\n  map,\n  mergeWith,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n  at,\n  getElement,\n  requestJSON,\n  setToggle,\n  watchDocument,\n  watchKeyboard,\n  watchLocation,\n  watchLocationTarget,\n  watchMedia,\n  watchPrint,\n  watchViewport\n} from \"./browser\"\nimport {\n  getComponentElement,\n  getComponentElements,\n  mountContent,\n  mountDialog,\n  mountHeader,\n  mountHeaderTitle,\n  mountSearch,\n  mountSidebar,\n  mountSource,\n  mountTableOfContents,\n  mountTabs,\n  watchHeader,\n  watchMain\n} from \"./components\"\nimport {\n  SearchIndex,\n  setupClipboardJS,\n  setupInstantLoading,\n  setupVersionSelector\n} from \"./integrations\"\nimport {\n  patchIndeterminate,\n  patchScrollfix,\n  patchScrolllock\n} from \"./patches\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$   = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$   = watchMedia(\"(min-width: 960px)\")\nconst screen$   = watchMedia(\"(min-width: 1220px)\")\nconst print$    = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n  ? __search?.index || requestJSON<SearchIndex>(\n    `${config.base}/search/search_index.json`\n  )\n  : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject<string>()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n  setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n  setupVersionSelector()\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n  .pipe(\n    delay(125)\n  )\n    .subscribe(() => {\n      setToggle(\"drawer\", false)\n      setToggle(\"search\", false)\n    })\n\n/* Set up global keyboard handlers */\nkeyboard$\n  .pipe(\n    filter(({ mode }) => mode === \"global\")\n  )\n    .subscribe(key => {\n      switch (key.type) {\n\n        /* Go to previous page */\n        case \"p\":\n        case \",\":\n          const prev = getElement(\"[href][rel=prev]\")\n          if (typeof prev !== \"undefined\")\n            prev.click()\n          break\n\n        /* Go to next page */\n        case \"n\":\n        case \".\":\n          const next = getElement(\"[href][rel=next]\")\n          if (typeof next !== \"undefined\")\n            next.click()\n          break\n      }\n    })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n  .pipe(\n    map(() => getComponentElement(\"main\")),\n    switchMap(el => watchMain(el, { viewport$, header$ })),\n    shareReplay(1)\n  )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n  /* Dialog */\n  ...getComponentElements(\"dialog\")\n    .map(el => mountDialog(el, { alert$ })),\n\n  /* Header */\n  ...getComponentElements(\"header\")\n    .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n  /* Search */\n  ...getComponentElements(\"search\")\n    .map(el => mountSearch(el, { index$, keyboard$ })),\n\n  /* Repository information */\n  ...getComponentElements(\"source\")\n    .map(el => mountSource(el)),\n\n  /* Navigation tabs */\n  ...getComponentElements(\"tabs\")\n    .map(el => mountTabs(el, { viewport$, header$ })),\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n  /* Content */\n  ...getComponentElements(\"content\")\n    .map(el => mountContent(el, { target$, viewport$, print$ })),\n\n  /* Header title */\n  ...getComponentElements(\"header-title\")\n    .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n  /* Sidebar */\n  ...getComponentElements(\"sidebar\")\n    .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n      ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n      : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n    ),\n\n  /* Table of contents */\n  ...getComponentElements(\"toc\")\n    .map(el => mountTableOfContents(el, { viewport$, header$ })),\n))\n\n/* Set up component observables */\nconst component$ = document$\n  .pipe(\n    switchMap(() => content$),\n    mergeWith(control$),\n    shareReplay(1)\n  )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$  = document$          /* Document observable */\nwindow.location$  = location$          /* Location subject */\nwindow.target$    = target$            /* Location target observable */\nwindow.keyboard$  = keyboard$          /* Keyboard observable */\nwindow.viewport$  = viewport$          /* Viewport observable */\nwindow.tablet$    = tablet$            /* Tablet observable */\nwindow.screen$    = screen$            /* Screen observable */\nwindow.print$     = print$             /* Print mode observable */\nwindow.alert$     = alert$             /* Alert subject */\nwindow.component$ = component$         /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n    extendStatics = Object.setPrototypeOf ||\r\n        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n        function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n    return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n    if (typeof b !== \"function\" && b !== null)\r\n        throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n    extendStatics(d, b);\r\n    function __() { this.constructor = d; }\r\n    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n    __assign = Object.assign || function __assign(t) {\r\n        for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n            s = arguments[i];\r\n            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n        }\r\n        return t;\r\n    }\r\n    return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n    var t = {};\r\n    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n        t[p] = s[p];\r\n    if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n                t[p[i]] = s[p[i]];\r\n        }\r\n    return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n    if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n    return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n    return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n    if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n    return new (P || (P = Promise))(function (resolve, reject) {\r\n        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n        function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n        step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n    });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n    return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n    function verb(n) { return function (v) { return step([n, v]); }; }\r\n    function step(op) {\r\n        if (f) throw new TypeError(\"Generator is already executing.\");\r\n        while (_) try {\r\n            if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n            if (y = 0, t) op = [op[0] & 2, t.value];\r\n            switch (op[0]) {\r\n                case 0: case 1: t = op; break;\r\n                case 4: _.label++; return { value: op[1], done: false };\r\n                case 5: _.label++; y = op[1]; op = [0]; continue;\r\n                case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n                default:\r\n                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n                    if (t[2]) _.ops.pop();\r\n                    _.trys.pop(); continue;\r\n            }\r\n            op = body.call(thisArg, _);\r\n        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n    }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n    if (k2 === undefined) k2 = k;\r\n    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n    if (k2 === undefined) k2 = k;\r\n    o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n    for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n    var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n    if (m) return m.call(o);\r\n    if (o && typeof o.length === \"number\") return {\r\n        next: function () {\r\n            if (o && i >= o.length) o = void 0;\r\n            return { value: o && o[i++], done: !o };\r\n        }\r\n    };\r\n    throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n    var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n    if (!m) return o;\r\n    var i = m.call(o), r, ar = [], e;\r\n    try {\r\n        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n    }\r\n    catch (error) { e = { error: error }; }\r\n    finally {\r\n        try {\r\n            if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n        }\r\n        finally { if (e) throw e.error; }\r\n    }\r\n    return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n    for (var ar = [], i = 0; i < arguments.length; i++)\r\n        ar = ar.concat(__read(arguments[i]));\r\n    return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n    for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n            r[k] = a[j];\r\n    return r;\r\n}\r\n\r\nexport function __spreadArray(to, from) {\r\n    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)\r\n        to[j] = from[i];\r\n    return to;\r\n}\r\n\r\nexport function __await(v) {\r\n    return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n    return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n    function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n    function fulfill(value) { resume(\"next\", value); }\r\n    function reject(value) { resume(\"throw\", value); }\r\n    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n    var i, p;\r\n    return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n    function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var m = o[Symbol.asyncIterator], i;\r\n    return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n    if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n    return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n    Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n    o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n    if (mod && mod.__esModule) return mod;\r\n    var result = {};\r\n    if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n    __setModuleDefault(result, mod);\r\n    return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n    return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, privateMap) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to get private field on non-instance\");\r\n    }\r\n    return privateMap.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, privateMap, value) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to set private field on non-instance\");\r\n    }\r\n    privateMap.set(receiver, value);\r\n    return value;\r\n}\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ReplaySubject, Subject, fromEvent } from \"rxjs\"\nimport { mapTo } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject<Document> {\n  const document$ = new ReplaySubject<Document>()\n  fromEvent(document, \"DOMContentLoaded\")\n    .pipe(\n      mapTo(document)\n    )\n      .subscribe(document$)\n\n  /* Return document */\n  return document$\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getElement<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T | undefined\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T | undefined {\n  return node.querySelector<T>(selector) || undefined\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T {\n  const el = getElement<T>(selector, node)\n  if (typeof el === \"undefined\")\n    throw new ReferenceError(\n      `Missing element: expected \"${selector}\" to be present`\n    )\n  return el\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n  return document.activeElement instanceof HTMLElement\n    ? document.activeElement\n    : undefined\n}\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T[]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T[] {\n  return Array.from(node.querySelectorAll<T>(selector))\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Create an element\n *\n * @template T - Tag name type\n *\n * @param tagName - Tag name\n *\n * @returns Element\n */\nexport function createElement<T extends keyof HTMLElementTagNameMap>(\n  tagName: T\n): HTMLElementTagNameMap[T] {\n  return document.createElement(tagName)\n}\n\n/**\n * Replace an element with the given list of nodes\n *\n * @param el - Element\n * @param nodes - Replacement nodes\n */\nexport function replaceElement(\n  el: HTMLElement, ...nodes: Node[]\n): void {\n  el.replaceWith(...nodes)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element focus\n *\n * @param el - Element\n * @param value - Whether the element should be focused\n */\nexport function setElementFocus(\n  el: HTMLElement, value = true\n): void {\n  if (value)\n    el.focus()\n  else\n    el.blur()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n  el: HTMLElement\n): Observable<boolean> {\n  return merge(\n    fromEvent<FocusEvent>(el, \"focus\"),\n    fromEvent<FocusEvent>(el, \"blur\")\n  )\n    .pipe(\n      map(({ type }) => type === \"focus\"),\n      startWith(el === getActiveElement())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n  width: number                        /* Element width */\n  height: number                       /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject<ResizeObserverEntry>()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n  new ResizeObserver(entries => {\n    for (const entry of entries)\n      entry$.next(entry)\n  })\n))\n  .pipe(\n    switchMap(resize => NEVER.pipe(startWith(resize))\n      .pipe(\n        finalize(() => resize.disconnect())\n      )\n    ),\n    shareReplay(1)\n  )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.offsetWidth,\n    height: el.offsetHeight\n  }\n}\n\n/**\n * Retrieve element content size, i.e. including overflowing content\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementContentSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.scrollWidth,\n    height: el.scrollHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n  el: HTMLElement\n): Observable<ElementSize> {\n  return observer$\n    .pipe(\n      tap(observer => observer.observe(el)),\n      switchMap(observer => entry$\n        .pipe(\n          filter(({ target }) => target === el),\n          finalize(() => observer.unobserve(el)),\n          map(({ contentRect }) => ({\n            width:  contentRect.width,\n            height: contentRect.height\n          }))\n        )\n      ),\n      startWith(getElementSize(el))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  map,\n  startWith\n} from \"rxjs/operators\"\n\nimport {\n  getElementContentSize,\n  getElementSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(el: HTMLElement): ElementOffset {\n  return {\n    x: el.scrollLeft,\n    y: el.scrollTop\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n  el: HTMLElement\n): Observable<ElementOffset> {\n  return merge(\n    fromEvent(el, \"scroll\"),\n    fromEvent(window, \"resize\")\n  )\n    .pipe(\n      map(() => getElementOffset(el)),\n      startWith(getElementOffset(el))\n    )\n}\n\n/**\n * Watch element threshold\n *\n * This function returns an observable which emits whether the bottom scroll\n * offset of an elements is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element threshold observable\n */\nexport function watchElementThreshold(\n  el: HTMLElement, threshold = 16\n): Observable<boolean> {\n  return watchElementOffset(el)\n    .pipe(\n      map(({ y }) => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return y >= (\n          content.height - visible.height - threshold\n        )\n      }),\n      distinctUntilChanged()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element text selection\n *\n * @param el - Element\n */\nexport function setElementSelection(\n  el: HTMLElement\n): void {\n  if (el instanceof HTMLInputElement)\n    el.select()\n  else\n    throw new Error(\"Not implemented\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getElementOrThrow } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n  | \"drawer\"                           /* Toggle for drawer */\n  | \"search\"                           /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record<Toggle, HTMLInputElement> = {\n  drawer: getElementOrThrow(\"[data-md-toggle=drawer]\"),\n  search: getElementOrThrow(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n  return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n  if (toggles[name].checked !== value)\n    toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable<boolean> {\n  const el = toggles[name]\n  return fromEvent(el, \"change\")\n    .pipe(\n      map(() => el.checked),\n      startWith(el.checked)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { filter, map, share } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n  | \"global\"                           /* Global */\n  | \"search\"                           /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n  mode: KeyboardMode                   /* Keyboard mode */\n  type: string                         /* Key type */\n  claim(): void                        /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(el: HTMLElement): boolean {\n  switch (el.tagName) {\n\n    /* Form elements */\n    case \"INPUT\":\n    case \"SELECT\":\n    case \"TEXTAREA\":\n      return true\n\n    /* Everything else */\n    default:\n      return el.isContentEditable\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable<Keyboard> {\n  return fromEvent<KeyboardEvent>(window, \"keydown\")\n    .pipe(\n      filter(ev => !(ev.metaKey || ev.ctrlKey)),\n      map(ev => ({\n        mode: getToggle(\"search\") ? \"search\" : \"global\",\n        type: ev.key,\n        claim() {\n          ev.preventDefault()\n          ev.stopPropagation()\n        }\n      } as Keyboard)),\n      filter(({ mode }) => {\n        if (mode === \"global\") {\n          const active = getActiveElement()\n          if (typeof active !== \"undefined\")\n            return !isSusceptibleToKeyboard(active)\n        }\n        return true\n      }),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n  return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n  location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject<URL> {\n  return new Subject<URL>()\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport { filter, map, share, startWith, switchMap } from \"rxjs/operators\"\n\nimport { createElement, getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n  return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n  const el = createElement(\"a\")\n  el.href = hash\n  el.addEventListener(\"click\", ev => ev.stopPropagation())\n  el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable<string> {\n  return fromEvent<HashChangeEvent>(window, \"hashchange\")\n    .pipe(\n      map(getLocationHash),\n      startWith(getLocationHash()),\n      filter(hash => hash.length > 0),\n      share()\n    )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable<HTMLElement> {\n  return watchLocationHash()\n    .pipe(\n      switchMap(id => of(getElement(`[id=\"${id}\"]`)!))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  filter,\n  map,\n  mapTo,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable<boolean> {\n  const media = matchMedia(query)\n  return fromEvent<MediaQueryListEvent>(media, \"change\")\n    .pipe(\n      map(ev => ev.matches),\n      startWith(media.matches)\n    )\n}\n\n/**\n * Watch print mode, cross-browser\n *\n * @returns Print mode observable\n */\nexport function watchPrint(): Observable<void> {\n  return merge(\n    watchMedia(\"print\").pipe(filter(Boolean)),  /* Webkit */\n    fromEvent(window, \"beforeprint\")            /* IE, FF */\n  )\n    .pipe(\n      mapTo(undefined)\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at<T>(\n  query$: Observable<boolean>, factory: () => Observable<T>\n): Observable<T> {\n  return query$\n    .pipe(\n      switchMap(active => active ? factory() : NEVER)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, from } from \"rxjs\"\nimport {\n  filter,\n  map,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n  url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable<Response> {\n  return from(fetch(url.toString(), options))\n    .pipe(\n      filter(res => res.status === 200),\n    )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON<T>(\n  url: URL | string, options?: RequestInit\n): Observable<T> {\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.json()),\n      shareReplay(1)\n    )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n  url: URL | string, options?: RequestInit\n): Observable<Document> {\n  const dom = new DOMParser()\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/xml\")),\n      shareReplay(1)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n  return {\n    x: Math.max(0, pageXOffset),\n    y: Math.max(0, pageYOffset)\n  }\n}\n\n/**\n * Set viewport offset\n *\n * @param offset - Viewport offset\n */\nexport function setViewportOffset(\n  { x, y }: Partial<ViewportOffset>\n): void {\n  window.scrollTo(x || 0, y || 0)\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable<ViewportOffset> {\n  return merge(\n    fromEvent(window, \"scroll\", { passive: true }),\n    fromEvent(window, \"resize\", { passive: true })\n  )\n    .pipe(\n      map(getViewportOffset),\n      startWith(getViewportOffset())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n  width: number                        /* Viewport width */\n  height: number                       /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n  return {\n    width:  innerWidth,\n    height: innerHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable<ViewportSize> {\n  return fromEvent(window, \"resize\", { passive: true })\n    .pipe(\n      map(getViewportSize),\n      startWith(getViewportSize())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, combineLatest } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  map,\n  shareReplay\n} from \"rxjs/operators\"\n\nimport { Header } from \"~/components\"\n\nimport {\n  ViewportOffset,\n  watchViewportOffset\n} from \"../offset\"\nimport {\n  ViewportSize,\n  watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n  offset: ViewportOffset               /* Viewport offset */\n  size: ViewportSize                   /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch at options\n */\ninterface WatchAtOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable<Viewport> {\n  return combineLatest([\n    watchViewportOffset(),\n    watchViewportSize()\n  ])\n    .pipe(\n      map(([offset, size]) => ({ offset, size })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n  el: HTMLElement, { viewport$, header$ }: WatchAtOptions\n): Observable<Viewport> {\n  const size$ = viewport$\n    .pipe(\n      distinctUntilKeyChanged(\"size\")\n    )\n\n  /* Compute element offset */\n  const offset$ = combineLatest([size$, header$])\n    .pipe(\n      map((): ViewportOffset => ({\n        x: el.offsetLeft,\n        y: el.offsetTop\n      }))\n    )\n\n  /* Compute relative viewport, return hot observable */\n  return combineLatest([header$, viewport$, offset$])\n    .pipe(\n      map(([{ height }, { offset, size }, { x, y }]) => ({\n        offset: {\n          x: offset.x - x,\n          y: offset.y - y + height\n        },\n        size\n      }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, fromEvent } from \"rxjs\"\nimport {\n  map,\n  share,\n  switchMapTo,\n  tap,\n  throttle\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n  type: unknown                        /* Message type */\n  data?: unknown                       /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n  T extends WorkerMessage\n> {\n  tx$: Subject<T>                      /* Message transmission subject */\n  rx$: Observable<T>                   /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions<T extends WorkerMessage> {\n  tx$: Observable<T>                   /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker<T extends WorkerMessage>(\n  worker: Worker, { tx$ }: WatchOptions<T>\n): Observable<T> {\n\n  /* Intercept messages from worker-like objects */\n  const rx$ = fromEvent<MessageEvent>(worker, \"message\")\n    .pipe(\n      map(({ data }) => data as T)\n    )\n\n  /* Send and receive messages, return hot observable */\n  return tx$\n    .pipe(\n      throttle(() => rx$, { leading: true, trailing: true }),\n      tap(message => worker.postMessage(message)),\n      switchMapTo(rx$),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n  | \"header.autohide\"                  /* Hide header */\n  | \"navigation.expand\"                /* Automatic expansion */\n  | \"navigation.instant\"               /* Instant loading */\n  | \"navigation.sections\"              /* Sections navigation */\n  | \"navigation.tabs\"                  /* Tabs navigation */\n  | \"toc.integrate\"                    /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n  | \"clipboard.copy\"                   /* Copy to clipboard */\n  | \"clipboard.copied\"                 /* Copied to clipboard */\n  | \"search.config.lang\"               /* Search language */\n  | \"search.config.pipeline\"           /* Search pipeline */\n  | \"search.config.separator\"          /* Search separator */\n  | \"search.placeholder\"               /* Search */\n  | \"search.result.placeholder\"        /* Type to start searching */\n  | \"search.result.none\"               /* No matching documents */\n  | \"search.result.one\"                /* 1 matching document */\n  | \"search.result.other\"              /* # matching documents */\n  | \"search.result.more.one\"           /* 1 more on this page */\n  | \"search.result.more.other\"         /* # more on this page */\n  | \"search.result.term.missing\"       /* Missing */\n\n/**\n * Translations\n */\nexport type Translations = Record<Translation, string>\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n  provider: \"mike\"                     /* Version provider */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n  base: string                         /* Base URL */\n  features: Flag[]                     /* Feature flags */\n  translations: Translations           /* Translations */\n  search: string                       /* Search worker URL */\n  version?: Versioning                 /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElementOrThrow(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = new URL(config.base, getLocation())\n  .toString()\n  .replace(/\\/$/, \"\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n  return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n  return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n  key: Translation, value?: string | number\n): string {\n  return typeof value !== \"undefined\"\n    ? config.translations[key].replace(\"#\", value.toString())\n    : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component\n */\nexport type ComponentType =\n  | \"announce\"                         /* Announcement bar */\n  | \"container\"                        /* Container */\n  | \"content\"                          /* Content */\n  | \"dialog\"                           /* Dialog */\n  | \"header\"                           /* Header */\n  | \"header-title\"                     /* Header title */\n  | \"header-topic\"                     /* Header topic */\n  | \"main\"                             /* Main area */\n  | \"search\"                           /* Search */\n  | \"search-query\"                     /* Search input */\n  | \"search-result\"                    /* Search results */\n  | \"sidebar\"                          /* Sidebar */\n  | \"skip\"                             /* Skip link */\n  | \"source\"                           /* Repository information */\n  | \"tabs\"                             /* Navigation tabs */\n  | \"toc\"                              /* Table of contents */\n\n/**\n * A component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n  T extends {} = {},\n  U extends HTMLElement = HTMLElement\n> =\n  T & {\n    ref: U                             /* Component reference */\n  }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n  \"announce\": HTMLElement              /* Announcement bar */\n  \"container\": HTMLElement             /* Container */\n  \"content\": HTMLElement               /* Content */\n  \"dialog\": HTMLElement                /* Dialog */\n  \"header\": HTMLElement                /* Header */\n  \"header-title\": HTMLElement          /* Header title */\n  \"header-topic\": HTMLElement          /* Header topic */\n  \"main\": HTMLElement                  /* Main area */\n  \"search\": HTMLElement                /* Search */\n  \"search-query\": HTMLInputElement     /* Search input */\n  \"search-result\": HTMLElement         /* Search results */\n  \"sidebar\": HTMLElement               /* Sidebar */\n  \"skip\": HTMLAnchorElement            /* Skip link */\n  \"source\": HTMLAnchorElement          /* Repository information */\n  \"tabs\": HTMLElement                  /* Navigation tabs */\n  \"toc\": HTMLElement                   /* Table of contents */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n  return getElementOrThrow(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n  return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  switchMap,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetFocusable, setFocusable } from \"~/actions\"\nimport {\n  Viewport,\n  getElementContentSize,\n  getElementSize,\n  getElements,\n  watchMedia\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n  scroll: boolean                      /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global index for Clipboard.js integration\n */\nlet index = 0\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n  el: HTMLElement, { viewport$ }: WatchOptions\n): Observable<CodeBlock> {\n  const container$ = of(el)\n    .pipe(\n      switchMap(child => {\n        const container = child.closest(\"[data-tabs]\")\n        if (container instanceof HTMLElement) {\n          return merge(\n            ...getElements(\"input\", container)\n              .map(input => fromEvent(input, \"change\"))\n          )\n        }\n        return NEVER\n      })\n    )\n\n  /* Check overflow on resize and tab change */\n  return merge(\n    viewport$.pipe(distinctUntilKeyChanged(\"size\")),\n    container$\n  )\n    .pipe(\n      map(() => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return {\n          scroll: content.width > visible.width\n        }\n      }),\n      distinctUntilKeyChanged(\"scroll\")\n    )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block component observable\n */\nexport function mountCodeBlock(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<CodeBlock>> {\n  const internal$ = new Subject<CodeBlock>()\n  internal$\n    .pipe(\n      withLatestFrom(watchMedia(\"(hover)\"))\n    )\n      .subscribe(([{ scroll }, hover]) => {\n        if (scroll && hover)\n          setFocusable(el)\n        else\n          resetFocusable(el)\n      })\n\n  /* Render button for Clipboard.js integration */\n  if (ClipboardJS.isSupported()) {\n    const parent = el.closest(\"pre\")!\n    parent.id = `__code_${index++}`\n    parent.insertBefore(\n      renderClipboardButton(parent.id),\n      el\n    )\n  }\n\n  /* Create and return component */\n  return watchCodeBlock(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set focusable property\n *\n * @param el - Element\n * @param value - Tabindex value\n */\nexport function setFocusable(\n  el: HTMLElement, value = 0\n): void {\n  el.setAttribute(\"tabindex\", value.toString())\n}\n\n/**\n * Reset focusable property\n *\n * @param el - Element\n */\nexport function resetFocusable(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"tabindex\")\n}\n\n/**\n * Set scroll lock\n *\n * @param el - Scrollable element\n * @param value - Vertical offset\n */\nexport function setScrollLock(\n  el: HTMLElement, value: number\n): void {\n  el.setAttribute(\"data-md-state\", \"lock\")\n  el.style.top = `-${value}px`\n}\n\n/**\n * Reset scroll lock\n *\n * @param el - Scrollable element\n */\nexport function resetScrollLock(\n  el: HTMLElement\n): void {\n  const value = -1 * parseInt(el.style.top, 10)\n  el.removeAttribute(\"data-md-state\")\n  el.style.top = \"\"\n  if (value)\n    window.scrollTo(0, value)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set anchor state\n *\n * @param el - Anchor element\n * @param state - Anchor state\n */\nexport function setAnchorState(\n  el: HTMLElement, state: \"blur\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset anchor state\n *\n * @param el - Anchor element\n */\nexport function resetAnchorState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set anchor active\n *\n * @param el - Anchor element\n * @param value - Whether the anchor is active\n */\nexport function setAnchorActive(\n  el: HTMLElement, value: boolean\n): void {\n  el.classList.toggle(\"md-nav__link--active\", value)\n}\n\n/**\n * Reset anchor active\n *\n * @param el - Anchor element\n */\nexport function resetAnchorActive(\n  el: HTMLElement\n): void {\n  el.classList.remove(\"md-nav__link--active\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set dialog message\n *\n * @param el - Dialog element\n * @param value - Dialog message\n */\nexport function setDialogMessage(\n  el: HTMLElement, value: string\n): void {\n  el.firstElementChild!.innerHTML = value\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set dialog state\n *\n * @param el - Dialog element\n * @param state - Dialog state\n */\nexport function setDialogState(\n  el: HTMLElement, state: \"open\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset dialog state\n *\n * @param el - Dialog element\n */\nexport function resetDialogState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header state\n *\n * @param el - Header element\n * @param state - Header state\n */\nexport function setHeaderState(\n  el: HTMLElement, state: \"shadow\" | \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header state\n *\n * @param el - Header element\n */\nexport function resetHeaderState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header title state\n *\n * @param el - Header title element\n * @param state - Header title state\n */\nexport function setHeaderTitleState(\n  el: HTMLElement, state: \"active\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header title state\n *\n * @param el - Header title element\n */\nexport function resetHeaderTitleState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set search query placeholder\n *\n * @param el - Search query element\n * @param value - Placeholder\n */\nexport function setSearchQueryPlaceholder(\n  el: HTMLInputElement, value: string\n): void {\n  el.placeholder = value\n}\n\n/**\n * Reset search query placeholder\n *\n * @param el - Search query element\n */\nexport function resetSearchQueryPlaceholder(\n  el: HTMLInputElement\n): void {\n  el.placeholder = translation(\"search.placeholder\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n  & JSXInternal.HTMLAttributes\n  & JSXInternal.SVGAttributes\n  & Record<string, any>\n\n/**\n * Child element\n */\ntype Child =\n  | HTMLElement\n  | Text\n  | string\n  | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n  /* Handle primitive types (including raw HTML) */\n  if (typeof child === \"string\" || typeof child === \"number\") {\n    el.innerHTML += child.toString()\n\n  /* Handle nodes */\n  } else if (child instanceof Node) {\n    el.appendChild(child)\n\n  /* Handle nested children */\n  } else if (Array.isArray(child)) {\n    for (const node of child)\n      appendChild(el, node)\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n  tag: string, attributes: Attributes | null, ...children: Child[]\n): HTMLElement {\n  const el = document.createElement(tag)\n\n  /* Set attributes, if any */\n  if (attributes)\n    for (const attr of Object.keys(attributes))\n      if (typeof attributes[attr] !== \"boolean\")\n        el.setAttribute(attr, attributes[attr])\n      else if (attributes[attr])\n        el.setAttribute(attr, \"\")\n\n  /* Append child nodes */\n  for (const child of children)\n    appendChild(el, child)\n\n  /* Return element */\n  return el\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n  namespace JSX {\n    type Element = HTMLElement\n    type IntrinsicElements = JSXInternal.IntrinsicElements\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n  let i = n\n  if (value.length > i) {\n    while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n    return `${value.substring(0, i)}...`\n  }\n  return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n  if (value > 999) {\n    const digits = +((value - 950) % 1000 > 99)\n    return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n  } else {\n    return value.toString()\n  }\n}\n\n/**\n * Simple hash function\n *\n * @see https://bit.ly/2wsVjJ4 - Original source\n *\n * @param value - Value to be hashed\n *\n * @returns Hash as 32bit integer\n */\nexport function hash(value: string): number {\n  let h = 0\n  for (let i = 0, len = value.length; i < len; i++) {\n    h  = ((h << 5) - h) + value.charCodeAt(i)\n    h |= 0 // Convert to 32bit integer\n  }\n  return h\n}\n\n/**\n * Add a digest to a value to ensure uniqueness\n *\n * When a single account hosts multiple sites on the same GitHub Pages domain,\n * entries would collide, because session and local storage are not unique.\n *\n * @param value - Value\n *\n * @returns Value with digest\n */\nexport function digest(value: string): string {\n  const config = configuration()\n  return `${value}[${hash(config.base)}]`\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set number of search results\n *\n * @param el - Search result metadata element\n * @param value - Number of results\n */\nexport function setSearchResultMeta(\n  el: HTMLElement, value: number\n): void {\n  switch (value) {\n\n    /* No results */\n    case 0:\n      el.textContent = translation(\"search.result.none\")\n      break\n\n    /* One result */\n    case 1:\n      el.textContent = translation(\"search.result.one\")\n      break\n\n    /* Multiple result */\n    default:\n      el.textContent = translation(\"search.result.other\", round(value))\n  }\n}\n\n/**\n * Reset number of search results\n *\n * @param el - Search result metadata element\n */\nexport function resetSearchResultMeta(\n  el: HTMLElement\n): void {\n  el.textContent = translation(\"search.result.placeholder\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Add an element to the search result list\n *\n * @param el - Search result list element\n * @param child - Search result element\n */\nexport function addToSearchResultList(\n  el: HTMLElement, child: Element\n): void {\n  el.appendChild(child)\n}\n\n/**\n * Reset search result list\n *\n * @param el - Search result list element\n */\nexport function resetSearchResultList(\n  el: HTMLElement\n): void {\n  el.innerHTML = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar offset\n *\n * @param el - Sidebar element\n * @param value - Sidebar offset\n */\nexport function setSidebarOffset(\n  el: HTMLElement, value: number\n): void {\n  el.style.top = `${value}px`\n}\n\n/**\n * Reset sidebar offset\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarOffset(\n  el: HTMLElement\n): void {\n  el.style.top = \"\"\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar height\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param value - Sidebar height\n */\nexport function setSidebarHeight(\n  el: HTMLElement, value: number\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`\n}\n\n/**\n * Reset sidebar height\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarHeight(\n  el: HTMLElement\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set repository facts\n *\n * @param el - Repository element\n * @param child - Repository facts element\n */\nexport function setSourceFacts(\n  el: HTMLElement, child: Element\n): void {\n  el.lastElementChild!.appendChild(child)\n}\n\n/**\n * Set repository state\n *\n * @param el - Repository element\n * @param state - Repository state\n */\nexport function setSourceState(\n  el: HTMLElement, state: \"done\"\n): void {\n  el.lastElementChild!.setAttribute(\"data-md-state\", state)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set tabs state\n *\n * @param el - Tabs element\n * @param state - Tabs state\n */\nexport function setTabsState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset tabs state\n *\n * @param el - Tabs element\n */\nexport function resetTabsState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n  return (\n    <button\n      class=\"md-clipboard md-icon\"\n      title={translation(\"clipboard.copy\")}\n      data-clipboard-target={`#${id} > code`}\n    ></button>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport {\n  SearchDocument,\n  SearchMetadata,\n  SearchResult\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n  TEASER = 1,                          /* Render teaser */\n  PARENT = 2                           /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n  document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n  const parent = flag & Flag.PARENT\n  const teaser = flag & Flag.TEASER\n\n  /* Render missing query terms */\n  const missing = Object.keys(document.terms)\n    .filter(key => !document.terms[key])\n    .map(key => [<del>{key}</del>, \" \"])\n    .flat()\n    .slice(0, -1)\n\n  /* Render article or section, depending on flags */\n  const url = document.location\n  return (\n    <a href={url} class=\"md-search-result__link\" tabIndex={-1}>\n      <article\n        class={[\"md-search-result__article\", ...parent\n          ? [\"md-search-result__article--document\"]\n          : []\n        ].join(\" \")}\n        data-md-score={document.score.toFixed(2)}\n      >\n        {parent > 0 && <div class=\"md-search-result__icon md-icon\"></div>}\n        <h1 class=\"md-search-result__title\">{document.title}</h1>\n        {teaser > 0 && document.text.length > 0 &&\n          <p class=\"md-search-result__teaser\">\n            {truncate(document.text, 320)}\n          </p>\n        }\n        {teaser > 0 && missing.length > 0 &&\n          <p class=\"md-search-result__terms\">\n            {translation(\"search.result.term.missing\")}: {...missing}\n          </p>\n        }\n      </article>\n    </a>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResult(\n  result: SearchResult\n): HTMLElement {\n  const threshold = result[0].score\n  const docs = [...result]\n\n  /* Find and extract parent article */\n  const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n  const [article] = docs.splice(parent, 1)\n\n  /* Determine last index above threshold */\n  let index = docs.findIndex(doc => doc.score < threshold)\n  if (index === -1)\n    index = docs.length\n\n  /* Partition sections */\n  const best = docs.slice(0, index)\n  const more = docs.slice(index)\n\n  /* Render children */\n  const children = [\n    renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n    ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n    ...more.length ? [\n      <details class=\"md-search-result__more\">\n        <summary tabIndex={-1}>\n          {more.length > 0 && more.length === 1\n            ? translation(\"search.result.more.one\")\n            : translation(\"search.result.more.other\", more.length)\n          }\n        </summary>\n        {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n      </details>\n    ] : []\n  ]\n\n  /* Render search result */\n  return (\n    <li class=\"md-search-result__item\">\n      {children}\n    </li>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n  return (\n    <ul class=\"md-source__facts\">\n      {facts.map(fact => (\n        <li class=\"md-source__fact\">{fact}</li>\n      ))}\n    </ul>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n  return (\n    <div class=\"md-typeset__scrollwrap\">\n      <div class=\"md-typeset__table\">\n        {table}\n      </div>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n  version: string                      /* Version identifier */\n  title: string                        /* Version title */\n  aliases: string[]                    /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n *\n * @returns Element\n */\nexport function renderVersionSelector(versions: Version[]): HTMLElement {\n  const config = configuration()\n\n  /* Determine active version */\n  const [, current] = config.base.match(/([^/]+)\\/?$/)!\n  const active =\n    versions.find(({ version, aliases }) => (\n      version === current || aliases.includes(current)\n    )) || versions[0]\n\n  /* Render version selector */\n  return (\n    <div class=\"md-version\">\n      <span class=\"md-version__current\">\n        {active.version}\n      </span>\n      <ul class=\"md-version__list\">\n        {versions.map(version => (\n          <li class=\"md-version__item\">\n            <a\n              class=\"md-version__link\"\n              href={`${new URL(version.version, config.base)}`}\n            >\n              {version.title}\n            </a>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject } from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  mapTo,\n  mergeWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n  el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable<Details> {\n  return target$\n    .pipe(\n      map(target => target.closest(\"details:not([open])\")!),\n      filter(details => el === details),\n      mergeWith(print$),\n      mapTo(el)\n    )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n  el: HTMLDetailsElement, options: MountOptions\n): Observable<Component<Details>> {\n  const internal$ = new Subject<Details>()\n  internal$.subscribe(() => {\n    el.setAttribute(\"open\", \"\")\n    el.scrollIntoView()\n  })\n\n  /* Create and return component */\n  return watchDetails(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      mapTo({ ref: el })\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { createElement, replaceElement } from \"~/browser\"\nimport { renderTable } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = createElement(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n  el: HTMLElement\n): Observable<Component<DataTable>> {\n  replaceElement(el, sentinel)\n  replaceElement(sentinel, renderTable(el))\n\n  /* Create and return component */\n  return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { Viewport, getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { CodeBlock, mountCodeBlock } from \"../code\"\nimport { Details, mountDetails } from \"../details\"\nimport { DataTable, mountDataTable } from \"../table\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n  | CodeBlock\n  | DataTable\n  | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n  el: HTMLElement, { target$, viewport$, print$ }: MountOptions\n): Observable<Component<Content>> {\n  return merge(\n\n    /* Code blocks */\n    ...getElements(\"pre > code\", el)\n      .map(child => mountCodeBlock(child, { viewport$ })),\n\n    /* Data tables */\n    ...getElements(\"table:not([class])\", el)\n      .map(child => mountDataTable(child)),\n\n    /* Details */\n    ...getElements(\"details\", el)\n      .map(child => mountDetails(child, { target$, print$ }))\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetDialogState,\n  setDialogMessage,\n  setDialogState\n} from \"~/actions\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n  message: string                      /* Dialog message */\n  open: boolean                        /* Dialog is visible */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n  _el: HTMLElement, { alert$ }: WatchOptions\n): Observable<Dialog> {\n  return alert$\n    .pipe(\n      switchMap(message => merge(\n        of(true),\n        of(false).pipe(delay(2000))\n      )\n        .pipe(\n          map(open => ({ message, open }))\n        )\n      )\n    )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right cornerwhen a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Dialog>> {\n  const internal$ = new Subject<Dialog>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ message, open }) => {\n        setDialogMessage(el, message)\n        if (open)\n          setDialogState(el, \"open\")\n        else\n          resetDialogState(el)\n      })\n\n  /* Create and return component */\n  return watchDialog(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  combineLatestWith,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  observeOn,\n  shareReplay,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { feature } from \"~/_\"\nimport { resetHeaderState, setHeaderState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n  height: number                       /* Header visible height */\n  sticky: boolean                      /* Header stickyness */\n  hidden: boolean                      /* User scrolled past threshold */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable<boolean> {\n  if (!feature(\"header.autohide\"))\n    return of(false)\n\n  /* Compute direction and turning point */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => [a < b, b] as const),\n      distinctUntilKeyChanged(0)\n    )\n\n  /* Compute whether header should be hidden */\n  const hidden$ = combineLatest([viewport$, direction$])\n    .pipe(\n      filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n      map(([, [direction]]) => direction),\n      distinctUntilChanged()\n    )\n\n  /* Compute threshold for autohiding */\n  const search$ = watchToggle(\"search\")\n  return combineLatest([viewport$, search$])\n    .pipe(\n      map(([{ offset }, search]) => offset.y > 400 && !search),\n      distinctUntilChanged(),\n      switchMap(active => active ? hidden$ : of(false)),\n      startWith(false)\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n  el: HTMLElement, options: WatchOptions\n): Observable<Header> {\n  return defer(() => {\n    const styles = getComputedStyle(el)\n    return of(\n      styles.position === \"sticky\" ||\n      styles.position === \"-webkit-sticky\"\n    )\n  })\n    .pipe(\n      combineLatestWith(watchElementSize(el), isHidden(options)),\n      map(([sticky, { height }, hidden]) => ({\n        height: sticky ? height : 0,\n        sticky,\n        hidden\n      })),\n      distinctUntilChanged((a, b) => (\n        a.sticky === b.sticky &&\n        a.height === b.height &&\n        a.hidden === b.hidden\n      )),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n  el: HTMLElement, { header$, main$ }: MountOptions\n): Observable<Component<Header>> {\n  const internal$ = new Subject<Main>()\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"active\"),\n      combineLatestWith(header$),\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(([{ active }, { hidden }]) => {\n        if (active)\n          setHeaderState(el, hidden ? \"hidden\" : \"shadow\")\n        else\n          resetHeaderState(el)\n      })\n\n  /* Connect to long-living subject and return component */\n  main$.subscribe(main => internal$.next(main))\n  return header$\n    .pipe(\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  animationFrameScheduler\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetHeaderTitleState,\n  setHeaderTitleState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n  active: boolean                      /* User scrolled past first headline */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n  el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions\n): Observable<HeaderTitle> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        const { height } = getElementSize(el)\n        return {\n          active: y >= height\n        }\n      }),\n      distinctUntilKeyChanged(\"active\")\n    )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<HeaderTitle>> {\n  const internal$ = new Subject<HeaderTitle>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ active }) => {\n        if (active)\n          setHeaderTitleState(el, \"active\")\n        else\n          resetHeaderTitleState(el)\n      })\n\n  /* Obtain headline, if any */\n  const headline = getElement<HTMLHeadingElement>(\"article h1\")\n  if (typeof headline === \"undefined\")\n    return NEVER\n\n  /* Create and return component */\n  return watchHeaderTitle(headline, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  map,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { Viewport, watchElementSize } from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n  offset: number                       /* Main area top offset */\n  height: number                       /* Main area visible height */\n  active: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Main> {\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(({ height }) => height),\n      distinctUntilChanged()\n    )\n\n  /* Compute the main area's top and bottom borders */\n  const border$ = adjust$\n    .pipe(\n      switchMap(() => watchElementSize(el)\n        .pipe(\n          map(({ height }) => ({\n            top:    el.offsetTop,\n            bottom: el.offsetTop + height\n          })),\n          distinctUntilKeyChanged(\"bottom\")\n        )\n      )\n    )\n\n  /* Compute the main area's offset, visible height and if we scrolled past */\n  return combineLatest([adjust$, border$, viewport$])\n    .pipe(\n      map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n        height = Math.max(0, height\n          - Math.max(0, top    - y,  header)\n          - Math.max(0, height + y - bottom)\n        )\n        return {\n          offset: top - header,\n          height,\n          active: top - header <= y\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.offset === b.offset &&\n        a.height === b.height &&\n        a.active === b.active\n      ))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport { Observable, Subject } from \"rxjs\"\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n  { alert$ }: SetupOptions\n): void {\n  if (ClipboardJS.isSupported()) {\n    new Observable<ClipboardJS.Event>(subscriber => {\n      new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\")\n        .on(\"success\", ev => subscriber.next(ev))\n    })\n      .subscribe(() => alert$.next(translation(\"clipboard.copied\")))\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  EMPTY,\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  catchError,\n  concatMap,\n  debounceTime,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  sample,\n  share,\n  skip,\n  skipUntil,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Viewport,\n  ViewportOffset,\n  createElement,\n  getElement,\n  getElements,\n  replaceElement,\n  request,\n  requestXML,\n  setLocation,\n  setLocationHash,\n  setViewportOffset\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n  url: URL                             /* State URL */\n  offset?: ViewportOffset              /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  document$: Subject<Document>         /* Document subject */\n  location$: Subject<URL>              /* Location subject */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns Processed URLs\n */\nfunction preprocess(urls: string[]): string[] {\n  if (urls.length < 2)\n    return urls\n\n  /* Take the first two URLs and remove everything after the last slash */\n  const [root, next] = urls\n    .sort((a, b) => a.length - b.length)\n    .map(url => url.replace(/[^/]+$/, \"\"))\n\n  /* Compute common prefix */\n  let index = 0\n  if (root === next)\n    index = root.length\n  else\n    while (root.charCodeAt(index) === next.charCodeAt(index))\n      index++\n\n  /* Replace common prefix (i.e. base) with effective base */\n  const config = configuration()\n  return urls.map(url => (\n    url.replace(root.slice(0, index), `${config.base}/`)\n  ))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n  { document$, location$, viewport$ }: SetupOptions\n): void {\n  const config = configuration()\n  if (location.protocol === \"file:\")\n    return\n\n  /* Disable automatic scroll restoration */\n  if (\"scrollRestoration\" in history) {\n    history.scrollRestoration = \"manual\"\n\n    /* Hack: ensure that reloads restore viewport offset */\n    fromEvent(window, \"beforeunload\")\n      .subscribe(() => {\n        history.scrollRestoration = \"auto\"\n      })\n  }\n\n  /* Hack: ensure absolute favicon link to omit 404s when switching */\n  const favicon = getElement<HTMLLinkElement>(\"link[rel='shortcut icon']\")\n  if (typeof favicon !== \"undefined\")\n    favicon.href = favicon.href\n\n  /* Intercept internal navigation */\n  const push$ = requestXML(`${config.base}/sitemap.xml`)\n    .pipe(\n      map(sitemap => preprocess(getElements(\"loc\", sitemap)\n        .map(node => node.textContent!)\n      )),\n      switchMap(urls => fromEvent<MouseEvent>(document.body, \"click\")\n        .pipe(\n          filter(ev => !ev.metaKey && !ev.ctrlKey),\n          switchMap(ev => {\n\n            /* Handle HTML and SVG elements */\n            if (ev.target instanceof Element) {\n              const el = ev.target.closest(\"a\")\n              if (el && !el.target && urls.includes(el.href)) {\n                ev.preventDefault()\n                return of({\n                  url: new URL(el.href)\n                })\n              }\n            }\n            return NEVER\n          })\n        )\n      ),\n      share<HistoryState>()\n    )\n\n  /* Intercept history back and forward */\n  const pop$ = fromEvent<PopStateEvent>(window, \"popstate\")\n    .pipe(\n      filter(ev => ev.state !== null),\n      map(ev => ({\n        url: new URL(location.href),\n        offset: ev.state\n      })),\n      share<HistoryState>()\n    )\n\n  /* Emit location change */\n  merge(push$, pop$)\n    .pipe(\n      distinctUntilChanged((a, b) => a.url.href === b.url.href),\n      map(({ url }) => url)\n    )\n      .subscribe(location$)\n\n  /* Fetch document via `XMLHTTPRequest` */\n  const response$ = location$\n    .pipe(\n      distinctUntilKeyChanged(\"pathname\"),\n      switchMap(url => request(url.href)\n        .pipe(\n          catchError(() => {\n            setLocation(url)\n            return NEVER\n          })\n        )\n      ),\n      share()\n    )\n\n  /* Set new location via `history.pushState` */\n  push$\n    .pipe(\n      sample(response$)\n    )\n      .subscribe(({ url }) => {\n        history.pushState({}, \"\", url.toString())\n      })\n\n  /* Parse and emit fetched document */\n  const dom = new DOMParser()\n  response$\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/html\"))\n    )\n      .subscribe(document$)\n\n  /* Emit history state change */\n  merge(push$, pop$)\n    .pipe(\n      sample(document$)\n    )\n      .subscribe(({ url, offset }) => {\n        if (url.hash && !offset)\n          setLocationHash(url.hash)\n        else\n          setViewportOffset(offset || { y: 0 })\n      })\n\n  /* Replace meta tags and components */\n  document$\n    .pipe(\n      skip(1)\n    )\n      .subscribe(replacement => {\n        for (const selector of [\n\n          /* Meta tags */\n          \"title\",\n          \"link[rel='canonical']\",\n          \"meta[name='author']\",\n          \"meta[name='description']\",\n\n          /* Components */\n          \"[data-md-component=announce]\",\n          \"[data-md-component=header-topic]\",\n          \"[data-md-component=container]\",\n          \"[data-md-component=skip]\"\n        ]) {\n          const source = getElement(selector)\n          const target = getElement(selector, replacement)\n          if (\n            typeof source !== \"undefined\" &&\n            typeof target !== \"undefined\"\n          ) {\n            replaceElement(source, target)\n          }\n        }\n      })\n\n  /* Re-evaluate scripts */\n  document$\n    .pipe(\n      skip(1),\n      map(() => getComponentElement(\"container\")),\n      switchMap(el => of(...getElements(\"script\", el))),\n      concatMap(el => {\n        const script = createElement(\"script\")\n        if (el.src) {\n          for (const name of el.getAttributeNames())\n            script.setAttribute(name, el.getAttribute(name)!)\n          replaceElement(el, script)\n\n          /* Complete when script is loaded */\n          return new Observable(observer => {\n            script.onload = () => observer.complete()\n          })\n\n        /* Complete immediately */\n        } else {\n          script.textContent = el.textContent!\n          replaceElement(el, script)\n          return EMPTY\n        }\n      })\n    )\n      .subscribe()\n\n  /* Debounce update of viewport offset */\n  viewport$\n    .pipe(\n      skipUntil(push$),\n      debounceTime(250),\n      distinctUntilKeyChanged(\"offset\")\n    )\n      .subscribe(({ offset }) => {\n        history.replaceState(offset, \"\")\n      })\n\n  /* Set viewport offset from history */\n  merge(push$, pop$)\n    .pipe(\n      bufferCount(2, 1),\n      filter(([a, b]) => a.url.pathname === b.url.pathname),\n      map(([, state]) => state)\n    )\n      .subscribe(({ offset }) => {\n        setViewportOffset(offset || { y: 0 })\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n *    that the resulting document must contain all terms, converting the query\n *    to an `AND` query (as opposed to the default `OR` behavior). While users\n *    may expect terms enclosed in quotation marks to map to span queries, i.e.\n *    for which order is important, Lunr.js doesn't support them, so the best\n *    we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n *    query or preceded by white space, or are not followed by a non-whitespace\n *    character or are at the end of the query string. Furthermore, filter\n *    unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n  return query\n    .split(/\"([^\"]+)\"/g)                            /* => 1 */\n      .map((terms, index) => index & 1\n        ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n        : terms\n      )\n      .join(\"\")\n    .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n    .trim()                                         /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ObservableInput, Subject, from } from \"rxjs\"\nimport { map, share } from \"rxjs/operators\"\n\nimport { configuration, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex, SearchIndexPipeline } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType,\n  SearchSetupMessage,\n  isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler<SearchMessage>\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex(\n  { config, docs, index }: SearchIndex\n): SearchIndex {\n\n  /* Override default language with value from translation */\n  if (config.lang.length === 1 && config.lang[0] === \"en\")\n    config.lang = [\n      translation(\"search.config.lang\")\n    ]\n\n  /* Override default separator with value from translation */\n  if (config.separator === \"[\\\\s\\\\-]+\")\n    config.separator = translation(\"search.config.separator\")\n\n  /* Set pipeline from translation */\n  const pipeline = translation(\"search.config.pipeline\")\n    .split(/\\s*,\\s*/)\n    .filter(Boolean) as SearchIndexPipeline\n\n  /* Return search index after defaulting */\n  return { config, docs, index, pipeline }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n  url: string, index: ObservableInput<SearchIndex>\n): SearchWorker {\n  const config = configuration()\n  const worker = new Worker(url)\n\n  /* Create communication channels and resolve relative links */\n  const tx$ = new Subject<SearchMessage>()\n  const rx$ = watchWorker(worker, { tx$ })\n    .pipe(\n      map(message => {\n        if (isSearchResultMessage(message)) {\n          for (const result of message.data)\n            for (const document of result)\n              document.location = `${config.base}/${document.location}`\n        }\n        return message\n      }),\n      share()\n    )\n\n  /* Set up search index */\n  from(index)\n    .pipe(\n      map<SearchIndex, SearchSetupMessage>(data => ({\n        type: SearchMessageType.SETUP,\n        data: setupSearchIndex(data)\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Return search worker */\n  return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { getElementOrThrow, requestJSON } from \"~/browser\"\nimport { Version, renderVersionSelector } from \"~/templates\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n */\nexport function setupVersionSelector(): void {\n  const config = configuration()\n  requestJSON<Version[]>(new URL(\"versions.json\", config.base))\n    .subscribe(versions => {\n      const topic = getElementOrThrow(\".md-header__topic\")\n      topic.appendChild(renderVersionSelector(versions))\n    })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  combineLatest,\n  fromEvent,\n  merge\n} from \"rxjs\"\nimport {\n  delay,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  takeLast,\n  takeUntil,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetSearchQueryPlaceholder,\n  setSearchQueryPlaceholder\n} from \"~/actions\"\nimport {\n  setElementFocus,\n  setToggle,\n  watchElementFocus\n} from \"~/browser\"\nimport {\n  SearchMessageType,\n  SearchQueryMessage,\n  SearchWorker,\n  defaultTransform\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n  value: string                        /* Query value */\n  focus: boolean                       /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n  el: HTMLInputElement\n): Observable<SearchQuery> {\n  const fn = __search?.transform || defaultTransform\n\n  /* Intercept focus and input events */\n  const focus$ = watchElementFocus(el)\n  const value$ = merge(\n    fromEvent(el, \"keyup\"),\n    fromEvent(el, \"focus\").pipe(delay(1))\n  )\n    .pipe(\n      map(() => fn(el.value)),\n      distinctUntilChanged()\n    )\n\n  /* Combine into single observable */\n  return combineLatest([value$, focus$])\n    .pipe(\n      map(([value, focus]) => ({ value, focus }))\n    )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n  el: HTMLInputElement, { tx$ }: SearchWorker\n): Observable<Component<SearchQuery, HTMLInputElement>> {\n  const internal$ = new Subject<SearchQuery>()\n\n  /* Handle value changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"value\"),\n      map(({ value }): SearchQueryMessage => ({\n        type: SearchMessageType.QUERY,\n        data: value\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Handle focus changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"focus\")\n    )\n      .subscribe(({ focus }) => {\n        if (focus) {\n          setToggle(\"search\", focus)\n          setSearchQueryPlaceholder(el, \"\")\n        } else {\n          resetSearchQueryPlaceholder(el)\n        }\n      })\n\n  /* Handle reset */\n  fromEvent(el.form!, \"reset\")\n    .pipe(\n      takeUntil(internal$.pipe(takeLast(1)))\n    )\n      .subscribe(() => setElementFocus(el))\n\n  /* Create and return component */\n  return watchSearchQuery(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  filter,\n  finalize,\n  map,\n  observeOn,\n  startWith,\n  switchMap,\n  tap,\n  withLatestFrom,\n  zipWith\n} from \"rxjs/operators\"\n\nimport {\n  addToSearchResultList,\n  resetSearchResultList,\n  resetSearchResultMeta,\n  setSearchResultMeta\n} from \"~/actions\"\nimport {\n  getElementOrThrow,\n  watchElementThreshold\n} from \"~/browser\"\nimport {\n  SearchResult as SearchResultData,\n  SearchWorker,\n  isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResult } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n  data: SearchResultData[]             /* Search result data */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  query$: Observable<SearchQuery>      /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n  el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable<Component<SearchResult>> {\n  const internal$ = new Subject<SearchResult>()\n  const boundary$ = watchElementThreshold(el.parentElement!)\n    .pipe(\n      filter(Boolean)\n    )\n\n  /* Update search result metadata */\n  const meta = getElementOrThrow(\":scope > :first-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(query$)\n    )\n      .subscribe(([{ data }, { value }]) => {\n        if (value)\n          setSearchResultMeta(meta, data.length)\n        else\n          resetSearchResultMeta(meta)\n      })\n\n  /* Update search result list */\n  const list = getElementOrThrow(\":scope > :last-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      tap(() => resetSearchResultList(list)),\n      switchMap(({ data }) => merge(\n        of(...data.slice(0, 10)),\n        of(...data.slice(10))\n          .pipe(\n            bufferCount(4),\n            zipWith(boundary$),\n            switchMap(([chunk]) => of(...chunk))\n          )\n      ))\n    )\n      .subscribe(result => {\n        addToSearchResultList(list, renderSearchResult(result))\n      })\n\n  /* Filter search result list */\n  const result$ = rx$\n    .pipe(\n      filter(isSearchResultMessage),\n      map(({ data }) => ({ data })),\n      startWith({ data: [] })\n    )\n\n  /* Create and return component */\n  return result$\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, ObservableInput, merge } from \"rxjs\"\nimport { filter, sample, take } from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Keyboard,\n  getActiveElement,\n  getElements,\n  setElementFocus,\n  setElementSelection,\n  setToggle\n} from \"~/browser\"\nimport {\n  SearchIndex,\n  isSearchQueryMessage,\n  isSearchReadyMessage,\n  setupSearchWorker\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\nimport { SearchQuery, mountSearchQuery } from \"../query\"\nimport { SearchResult, mountSearchResult } from \"../result\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n  | SearchQuery\n  | SearchResult\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  index$: ObservableInput<SearchIndex> /* Search index observable */\n  keyboard$: Observable<Keyboard>      /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n  el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable<Component<Search>> {\n  const config = configuration()\n  const worker = setupSearchWorker(config.search, index$)\n\n  /* Retrieve nested components */\n  const query  = getComponentElement(\"search-query\", el)\n  const result = getComponentElement(\"search-result\", el)\n\n  /* Re-emit query when search is ready */\n  const { tx$, rx$ } = worker\n  tx$\n    .pipe(\n      filter(isSearchQueryMessage),\n      sample(rx$.pipe(filter(isSearchReadyMessage))),\n      take(1)\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Set up search keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"search\")\n    )\n      .subscribe(key => {\n        const active = getActiveElement()\n        switch (key.type) {\n\n          /* Enter: prevent form submission */\n          case \"Enter\":\n            if (active === query)\n              key.claim()\n            break\n\n          /* Escape or Tab: close search */\n          case \"Escape\":\n          case \"Tab\":\n            setToggle(\"search\", false)\n            setElementFocus(query, false)\n            break\n\n          /* Vertical arrows: select previous or next search result */\n          case \"ArrowUp\":\n          case \"ArrowDown\":\n            if (typeof active === \"undefined\") {\n              setElementFocus(query)\n            } else {\n              const els = [query, ...getElements(\n                \":not(details) > [href], summary, details[open] [href]\",\n                result\n              )]\n              const i = Math.max(0, (\n                Math.max(0, els.indexOf(active)) + els.length + (\n                  key.type === \"ArrowUp\" ? -1 : +1\n                )\n              ) % els.length)\n              setElementFocus(els[i])\n            }\n\n            /* Prevent scrolling of page */\n            key.claim()\n            break\n\n          /* All other keys: hand to search query */\n          default:\n            if (query !== getActiveElement())\n              setElementFocus(query)\n        }\n      })\n\n  /* Set up global keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"global\"),\n    )\n      .subscribe(key => {\n        switch (key.type) {\n\n          /* Open search and select query */\n          case \"f\":\n          case \"s\":\n          case \"/\":\n            setElementFocus(query)\n            setElementSelection(query)\n            key.claim()\n            break\n        }\n      })\n\n  /* Create and return component */\n  const query$ = mountSearchQuery(query, worker)\n  return merge(\n    query$,\n    mountSearchResult(result, worker, { query$ })\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  finalize,\n  map,\n  observeOn,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport {\n  resetSidebarHeight,\n  resetSidebarOffset,\n  setSidebarHeight,\n  setSidebarOffset\n} from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n  height: number                       /* Sidebar height */\n  locked: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n  el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<Sidebar> {\n  const adjust =\n    el.parentElement!.offsetTop -\n    el.parentElement!.parentElement!.offsetTop\n\n  /* Compute the sidebar's available height and if it should be locked */\n  return combineLatest([main$, viewport$])\n    .pipe(\n      map(([{ offset, height }, { offset: { y } }]) => {\n        height = height\n          + Math.min(adjust, Math.max(0, y - offset))\n          - adjust\n        return {\n          height,\n          locked: y >= offset + adjust\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.height === b.height &&\n        a.locked === b.locked\n      ))\n    )\n}\n\n/**\n * Mount sidebar\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n  el: HTMLElement, { header$, ...options }: MountOptions\n): Observable<Component<Sidebar>> {\n  const internal$ = new Subject<Sidebar>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(header$)\n    )\n      .subscribe({\n\n        /* Update height and offset */\n        next([{ height }, { height: offset }]) {\n          setSidebarHeight(el, height)\n          setSidebarOffset(el, offset)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetSidebarOffset(el)\n          resetSidebarHeight(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchSidebar(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\nimport { round } from \"~/utilities\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n  user: string, repo?: string\n): Observable<SourceFacts> {\n  const url = typeof repo !== \"undefined\"\n    ? `https://api.github.com/repos/${user}/${repo}`\n    : `https://api.github.com/users/${user}`\n  return requestJSON<Repo & User>(url)\n    .pipe(\n      map(data => {\n\n        /* GitHub repository */\n        if (typeof repo !== \"undefined\") {\n          const { stargazers_count, forks_count }: Repo = data\n          return [\n            `${round(stargazers_count!)} Stars`,\n            `${round(forks_count!)} Forks`\n          ]\n\n        /* GitHub user/organization */\n        } else {\n          const { public_repos }: User = data\n          return [\n            `${round(public_repos!)} Repositories`\n          ]\n        }\n      }),\n      defaultIfEmpty([])\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\nimport { round } from \"~/utilities\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n  base: string, project: string\n): Observable<SourceFacts> {\n  const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n  return requestJSON<ProjectSchema>(url)\n    .pipe(\n      map(({ star_count, forks_count }) => ([\n        `${round(star_count)} Stars`,\n        `${round(forks_count)} Forks`\n      ])),\n      defaultIfEmpty([])\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts = string[]\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n  url: string\n): Observable<SourceFacts> {\n  const [type] = url.match(/(git(?:hub|lab))/i) || []\n  switch (type.toLowerCase()) {\n\n    /* GitHub repository */\n    case \"github\":\n      const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n      return fetchSourceFactsFromGitHub(user, repo)\n\n    /* GitLab repository */\n    case \"gitlab\":\n      const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n      return fetchSourceFactsFromGitLab(base, slug)\n\n    /* Everything else */\n    default:\n      return NEVER\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, Subject, defer, of } from \"rxjs\"\nimport {\n  catchError,\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  tap\n} from \"rxjs/operators\"\n\nimport { setSourceFacts, setSourceState } from \"~/actions\"\nimport { renderSourceFacts } from \"~/templates\"\nimport { digest } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\nimport { SourceFacts, fetchSourceFacts } from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n  facts: SourceFacts                   /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable<Source>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n  el: HTMLAnchorElement\n): Observable<Source> {\n  return fetch$ ||= defer(() => {\n    const data = sessionStorage.getItem(digest(\"__repo\"))\n    if (data) {\n      return of<SourceFacts>(JSON.parse(data))\n    } else {\n      const value$ = fetchSourceFacts(el.href)\n      value$.subscribe(value => {\n        try {\n          sessionStorage.setItem(digest(\"__repo\"), JSON.stringify(value))\n        } catch (err) {\n          /* Uncritical, just swallow */\n        }\n      })\n\n      /* Return value */\n      return value$\n    }\n  })\n    .pipe(\n      catchError(() => NEVER),\n      filter(facts => facts.length > 0),\n      map(facts => ({ facts })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n  el: HTMLAnchorElement\n): Observable<Component<Source>> {\n  const internal$ = new Subject<Source>()\n  internal$.subscribe(({ facts }) => {\n    setSourceFacts(el, renderSourceFacts(facts))\n    setSourceState(el, \"done\")\n  })\n\n  /* Create and return component */\n  return watchSource(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, animationFrameScheduler } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetTabsState, setTabsState } from \"~/actions\"\nimport { Viewport, watchViewportAt } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n  hidden: boolean                      /* User scrolled past tabs */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Tabs> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        return {\n          hidden: y >= 10\n        }\n      }),\n      distinctUntilKeyChanged(\"hidden\")\n    )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Tabs>> {\n  const internal$ = new Subject<Tabs>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setTabsState(el, \"hidden\")\n          else\n            resetTabsState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetTabsState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchTabs(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  scan,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetAnchorActive,\n  resetAnchorState,\n  setAnchorActive,\n  setAnchorState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElements,\n  watchElementSize\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n  prev: HTMLAnchorElement[][]          /* Anchors (previous) */\n  next: HTMLAnchorElement[][]          /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param anchors - Anchor elements\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n  anchors: HTMLAnchorElement[], { viewport$, header$ }: WatchOptions\n): Observable<TableOfContents> {\n  const table = new Map<HTMLAnchorElement, HTMLElement>()\n  for (const anchor of anchors) {\n    const id = decodeURIComponent(anchor.hash.substring(1))\n    const target = getElement(`[id=\"${id}\"]`)\n    if (typeof target !== \"undefined\")\n      table.set(anchor, target)\n  }\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(header => 24 + header.height)\n    )\n\n  /* Compute partition of previous and next anchors */\n  const partition$ = watchElementSize(document.body)\n    .pipe(\n      distinctUntilKeyChanged(\"height\"),\n\n      /* Build index to map anchor paths to vertical offsets */\n      map(() => {\n        let path: HTMLAnchorElement[] = []\n        return [...table].reduce((index, [anchor, target]) => {\n          while (path.length) {\n            const last = table.get(path[path.length - 1])!\n            if (last.tagName >= target.tagName) {\n              path.pop()\n            } else {\n              break\n            }\n          }\n\n          /* If the current anchor is hidden, continue with its parent */\n          let offset = target.offsetTop\n          while (!offset && target.parentElement) {\n            target = target.parentElement\n            offset = target.offsetTop\n          }\n\n          /* Map reversed anchor path to vertical offset */\n          return index.set(\n            [...path = [...path, anchor]].reverse(),\n            offset\n          )\n        }, new Map<HTMLAnchorElement[], number>())\n      }),\n\n      /* Re-compute partition when viewport offset changes */\n      switchMap(index => combineLatest([adjust$, viewport$])\n        .pipe(\n          scan(([prev, next], [adjust, { offset: { y } }]) => {\n\n            /* Look forward */\n            while (next.length) {\n              const [, offset] = next[0]\n              if (offset - adjust < y) {\n                prev = [...prev, next.shift()!]\n              } else {\n                break\n              }\n            }\n\n            /* Look backward */\n            while (prev.length) {\n              const [, offset] = prev[prev.length - 1]\n              if (offset - adjust >= y) {\n                next = [prev.pop()!, ...next]\n              } else {\n                break\n              }\n            }\n\n            /* Return partition */\n            return [prev, next]\n          }, [[], [...index]]),\n          distinctUntilChanged((a, b) => (\n            a[0] === b[0] &&\n            a[1] === b[1]\n          ))\n        )\n      )\n    )\n\n  /* Compute and return anchor list migrations */\n  return partition$\n    .pipe(\n      map(([prev, next]) => ({\n        prev: prev.map(([path]) => path),\n        next: next.map(([path]) => path)\n      })),\n\n      /* Extract anchor list migrations */\n      startWith({ prev: [], next: [] }),\n      bufferCount(2, 1),\n      map(([a, b]) => {\n\n        /* Moving down */\n        if (a.prev.length < b.prev.length) {\n          return {\n            prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n            next: []\n          }\n\n        /* Moving up */\n        } else {\n          return {\n            prev: b.prev.slice(-1),\n            next: b.next.slice(0, b.next.length - a.next.length)\n          }\n        }\n      })\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Anchor list element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<TableOfContents>> {\n  const internal$ = new Subject<TableOfContents>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n    )\n      .subscribe(({ prev, next }) => {\n\n        /* Look forward */\n        for (const [anchor] of next) {\n          resetAnchorActive(anchor)\n          resetAnchorState(anchor)\n        }\n\n        /* Look backward */\n        for (const [index, [anchor]] of prev.entries()) {\n          setAnchorActive(anchor, index === prev.length - 1)\n          setAnchorState(anchor, \"blur\")\n        }\n      })\n\n  /* Create and return component */\n  const anchors = getElements<HTMLAnchorElement>(\"[href^=\\\\#]\", el)\n  return watchTableOfContents(anchors, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  mapTo,\n  mergeMap,\n  switchMap,\n  takeWhile,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n  { document$, tablet$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements<HTMLInputElement>(\n        \"[data-md-state=indeterminate]\"\n      ))),\n      tap(el => {\n        el.indeterminate = true\n        el.checked = false\n      }),\n      mergeMap(el => fromEvent(el, \"change\")\n        .pipe(\n          takeWhile(() => el.hasAttribute(\"data-md-state\")),\n          mapTo(el)\n        )\n      ),\n      withLatestFrom(tablet$)\n    )\n      .subscribe(([el, tablet]) => {\n        el.removeAttribute(\"data-md-state\")\n        if (tablet)\n          el.checked = false\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  filter,\n  mapTo,\n  mergeMap,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n  return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n  { document$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements(\"[data-md-scrollfix]\"))),\n      tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n      filter(isAppleDevice),\n      mergeMap(el => fromEvent(el, \"touchstart\")\n        .pipe(\n          mapTo(el)\n        )\n      )\n    )\n      .subscribe(el => {\n        const top = el.scrollTop\n\n        /* We're at the top of the container */\n        if (top === 0) {\n          el.scrollTop = 1\n\n        /* We're at the bottom of the container */\n        } else if (top + el.offsetHeight === el.scrollHeight) {\n          el.scrollTop = top - 1\n        }\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  animationFrameScheduler,\n  combineLatest,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  map,\n  observeOn,\n  switchMap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetScrollLock, setScrollLock } from \"~/actions\"\nimport { Viewport, watchToggle } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n  { viewport$, tablet$ }: PatchOptions\n): void {\n  combineLatest([watchToggle(\"search\"), tablet$])\n    .pipe(\n      map(([active, tablet]) => active && !tablet),\n      switchMap(active => of(active)\n        .pipe(\n          delay(active ? 400 : 100),\n          observeOn(animationFrameScheduler)\n        )\n      ),\n      withLatestFrom(viewport$)\n    )\n      .subscribe(([active, { offset: { y }}]) => {\n        if (active)\n          setScrollLock(document.body, y)\n        else\n          resetScrollLock(document.body)\n      })\n}\n"],
-  "mappings": "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",
-  "names": []
-}
diff --git a/latest/assets/javascripts/bundle.d892486b.min.js b/latest/assets/javascripts/bundle.d892486b.min.js
new file mode 100644 (file)
index 0000000..a7d2639
--- /dev/null
@@ -0,0 +1,32 @@
+(()=>{var Ri=Object.create,ut=Object.defineProperty,Pi=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty,$i=Object.getOwnPropertyNames,Vi=Object.getOwnPropertyDescriptor,lt=Object.getOwnPropertySymbols,xr=Object.prototype.propertyIsEnumerable;var Sr=(e,t,r)=>t in e?ut(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))zt.call(t,r)&&Sr(e,r,t[r]);if(lt)for(var r of lt(t))xr.call(t,r)&&Sr(e,r,t[r]);return e},Di=e=>ut(e,"__esModule",{value:!0});var wr=(e,t)=>{var r={};for(var n in e)zt.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&lt)for(var n of lt(e))t.indexOf(n)<0&&xr.call(e,n)&&(r[n]=e[n]);return r},ft=(e,t)=>()=>(t||(t={exports:{}},e(t.exports,t)),t.exports);var Ui=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $i(t))!zt.call(e,n)&&n!=="default"&&ut(e,n,{get:()=>t[n],enumerable:!(r=Vi(t,n))||r.enumerable});return e},Be=e=>Ui(Di(ut(e!=null?Ri(Pi(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var Or=ft((Qt,Er)=>{(function(e,t){typeof Qt=="object"&&typeof Er!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Qt,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var at=T.type,st=T.tagName;return!!(st==="INPUT"&&a[at]&&!T.readOnly||st==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function u(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function l(T){!T.hasAttribute("data-focus-visible-added")||(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function m(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&u(r.activeElement),n=!0)}function f(T){n=!1}function h(T){!s(T.target)||(n||c(T.target))&&u(T.target)}function y(T){!s(T.target)||(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),l(T.target))}function b(T){document.visibilityState==="hidden"&&(o&&(n=!0),z())}function z(){document.addEventListener("mousemove",C),document.addEventListener("mousedown",C),document.addEventListener("mouseup",C),document.addEventListener("pointermove",C),document.addEventListener("pointerdown",C),document.addEventListener("pointerup",C),document.addEventListener("touchmove",C),document.addEventListener("touchstart",C),document.addEventListener("touchend",C)}function P(){document.removeEventListener("mousemove",C),document.removeEventListener("mousedown",C),document.removeEventListener("mouseup",C),document.removeEventListener("pointermove",C),document.removeEventListener("pointerdown",C),document.removeEventListener("pointerup",C),document.removeEventListener("touchmove",C),document.removeEventListener("touchstart",C),document.removeEventListener("touchend",C)}function C(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,P())}document.addEventListener("keydown",m,!0),document.addEventListener("mousedown",f,!0),document.addEventListener("pointerdown",f,!0),document.addEventListener("touchstart",f,!0),document.addEventListener("visibilitychange",b,!0),z(),r.addEventListener("focus",h,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Qr=ft((za,pt)=>{var Tr,_r,Mr,Ar,Lr,Hr,Cr,kr,jr,mt,qt,Fr,Ir,Rr,Pe,Pr,$r,Vr,Dr,Ur,Nr,Wr,zr,dt;(function(e){var t=typeof global=="object"?global:typeof self=="object"?self:typeof this=="object"?this:{};typeof define=="function"&&define.amd?define("tslib",["exports"],function(n){e(r(t,r(n)))}):typeof pt=="object"&&typeof pt.exports=="object"?e(r(t,r(pt.exports))):e(r(t));function r(n,o){return n!==t&&(typeof Object.create=="function"?Object.defineProperty(n,"__esModule",{value:!0}):n.__esModule=!0),function(i,a){return n[i]=o?o(i,a):a}}})(function(e){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,o){n.__proto__=o}||function(n,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(n[i]=o[i])};Tr=function(n,o){if(typeof o!="function"&&o!==null)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");t(n,o);function i(){this.constructor=n}n.prototype=o===null?Object.create(o):(i.prototype=o.prototype,new i)},_r=Object.assign||function(n){for(var o,i=1,a=arguments.length;i<a;i++){o=arguments[i];for(var s in o)Object.prototype.hasOwnProperty.call(o,s)&&(n[s]=o[s])}return n},Mr=function(n,o){var i={};for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&o.indexOf(a)<0&&(i[a]=n[a]);if(n!=null&&typeof Object.getOwnPropertySymbols=="function")for(var s=0,a=Object.getOwnPropertySymbols(n);s<a.length;s++)o.indexOf(a[s])<0&&Object.prototype.propertyIsEnumerable.call(n,a[s])&&(i[a[s]]=n[a[s]]);return i},Ar=function(n,o,i,a){var s=arguments.length,c=s<3?o:a===null?a=Object.getOwnPropertyDescriptor(o,i):a,u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(n,o,i,a);else for(var l=n.length-1;l>=0;l--)(u=n[l])&&(c=(s<3?u(c):s>3?u(o,i,c):u(o,i))||c);return s>3&&c&&Object.defineProperty(o,i,c),c},Lr=function(n,o){return function(i,a){o(i,a,n)}},Hr=function(n,o){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(n,o)},Cr=function(n,o,i,a){function s(c){return c instanceof i?c:new i(function(u){u(c)})}return new(i||(i=Promise))(function(c,u){function l(h){try{f(a.next(h))}catch(y){u(y)}}function m(h){try{f(a.throw(h))}catch(y){u(y)}}function f(h){h.done?c(h.value):s(h.value).then(l,m)}f((a=a.apply(n,o||[])).next())})},kr=function(n,o){var i={label:0,sent:function(){if(c[0]&1)throw c[1];return c[1]},trys:[],ops:[]},a,s,c,u;return u={next:l(0),throw:l(1),return:l(2)},typeof Symbol=="function"&&(u[Symbol.iterator]=function(){return this}),u;function l(f){return function(h){return m([f,h])}}function m(f){if(a)throw new TypeError("Generator is already executing.");for(;i;)try{if(a=1,s&&(c=f[0]&2?s.return:f[0]?s.throw||((c=s.return)&&c.call(s),0):s.next)&&!(c=c.call(s,f[1])).done)return c;switch(s=0,c&&(f=[f[0]&2,c.value]),f[0]){case 0:case 1:c=f;break;case 4:return i.label++,{value:f[1],done:!1};case 5:i.label++,s=f[1],f=[0];continue;case 7:f=i.ops.pop(),i.trys.pop();continue;default:if(c=i.trys,!(c=c.length>0&&c[c.length-1])&&(f[0]===6||f[0]===2)){i=0;continue}if(f[0]===3&&(!c||f[1]>c[0]&&f[1]<c[3])){i.label=f[1];break}if(f[0]===6&&i.label<c[1]){i.label=c[1],c=f;break}if(c&&i.label<c[2]){i.label=c[2],i.ops.push(f);break}c[2]&&i.ops.pop(),i.trys.pop();continue}f=o.call(n,i)}catch(h){f=[6,h],s=0}finally{a=c=0}if(f[0]&5)throw f[1];return{value:f[0]?f[1]:void 0,done:!0}}},jr=function(n,o){for(var i in n)i!=="default"&&!Object.prototype.hasOwnProperty.call(o,i)&&dt(o,n,i)},dt=Object.create?function(n,o,i,a){a===void 0&&(a=i),Object.defineProperty(n,a,{enumerable:!0,get:function(){return o[i]}})}:function(n,o,i,a){a===void 0&&(a=i),n[a]=o[i]},mt=function(n){var o=typeof Symbol=="function"&&Symbol.iterator,i=o&&n[o],a=0;if(i)return i.call(n);if(n&&typeof n.length=="number")return{next:function(){return n&&a>=n.length&&(n=void 0),{value:n&&n[a++],done:!n}}};throw new TypeError(o?"Object is not iterable.":"Symbol.iterator is not defined.")},qt=function(n,o){var i=typeof Symbol=="function"&&n[Symbol.iterator];if(!i)return n;var a=i.call(n),s,c=[],u;try{for(;(o===void 0||o-- >0)&&!(s=a.next()).done;)c.push(s.value)}catch(l){u={error:l}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(u)throw u.error}}return c},Fr=function(){for(var n=[],o=0;o<arguments.length;o++)n=n.concat(qt(arguments[o]));return n},Ir=function(){for(var n=0,o=0,i=arguments.length;o<i;o++)n+=arguments[o].length;for(var a=Array(n),s=0,o=0;o<i;o++)for(var c=arguments[o],u=0,l=c.length;u<l;u++,s++)a[s]=c[u];return a},Rr=function(n,o){for(var i=0,a=o.length,s=n.length;i<a;i++,s++)n[s]=o[i];return n},Pe=function(n){return this instanceof Pe?(this.v=n,this):new Pe(n)},Pr=function(n,o,i){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var a=i.apply(n,o||[]),s,c=[];return s={},u("next"),u("throw"),u("return"),s[Symbol.asyncIterator]=function(){return this},s;function u(b){a[b]&&(s[b]=function(z){return new Promise(function(P,C){c.push([b,z,P,C])>1||l(b,z)})})}function l(b,z){try{m(a[b](z))}catch(P){y(c[0][3],P)}}function m(b){b.value instanceof Pe?Promise.resolve(b.value.v).then(f,h):y(c[0][2],b)}function f(b){l("next",b)}function h(b){l("throw",b)}function y(b,z){b(z),c.shift(),c.length&&l(c[0][0],c[0][1])}},$r=function(n){var o,i;return o={},a("next"),a("throw",function(s){throw s}),a("return"),o[Symbol.iterator]=function(){return this},o;function a(s,c){o[s]=n[s]?function(u){return(i=!i)?{value:Pe(n[s](u)),done:s==="return"}:c?c(u):u}:c}},Vr=function(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=n[Symbol.asyncIterator],i;return o?o.call(n):(n=typeof mt=="function"?mt(n):n[Symbol.iterator](),i={},a("next"),a("throw"),a("return"),i[Symbol.asyncIterator]=function(){return this},i);function a(c){i[c]=n[c]&&function(u){return new Promise(function(l,m){u=n[c](u),s(l,m,u.done,u.value)})}}function s(c,u,l,m){Promise.resolve(m).then(function(f){c({value:f,done:l})},u)}},Dr=function(n,o){return Object.defineProperty?Object.defineProperty(n,"raw",{value:o}):n.raw=o,n};var r=Object.create?function(n,o){Object.defineProperty(n,"default",{enumerable:!0,value:o})}:function(n,o){n.default=o};Ur=function(n){if(n&&n.__esModule)return n;var o={};if(n!=null)for(var i in n)i!=="default"&&Object.prototype.hasOwnProperty.call(n,i)&&dt(o,n,i);return r(o,n),o},Nr=function(n){return n&&n.__esModule?n:{default:n}},Wr=function(n,o){if(!o.has(n))throw new TypeError("attempted to get private field on non-instance");return o.get(n)},zr=function(n,o,i){if(!o.has(n))throw new TypeError("attempted to set private field on non-instance");return o.set(n,i),i},e("__extends",Tr),e("__assign",_r),e("__rest",Mr),e("__decorate",Ar),e("__param",Lr),e("__metadata",Hr),e("__awaiter",Cr),e("__generator",kr),e("__exportStar",jr),e("__createBinding",dt),e("__values",mt),e("__read",qt),e("__spread",Fr),e("__spreadArrays",Ir),e("__spreadArray",Rr),e("__await",Pe),e("__asyncGenerator",Pr),e("__asyncDelegator",$r),e("__asyncValues",Vr),e("__makeTemplateObject",Dr),e("__importStar",Ur),e("__importDefault",Nr),e("__classPrivateFieldGet",Wr),e("__classPrivateFieldSet",zr)})});var lr=ft((it,ur)=>{(function(t,r){typeof it=="object"&&typeof ur=="object"?ur.exports=r():typeof define=="function"&&define.amd?define([],r):typeof it=="object"?it.ClipboardJS=r():t.ClipboardJS=r()})(it,function(){return function(){var e={134:function(n,o,i){"use strict";i.d(o,{default:function(){return Fi}});var a=i(279),s=i.n(a),c=i(370),u=i.n(c),l=i(817),m=i.n(l);function f(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?f=function(d){return typeof d}:f=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},f(w)}function h(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function y(w,v){for(var d=0;d<v.length;d++){var A=v[d];A.enumerable=A.enumerable||!1,A.configurable=!0,"value"in A&&(A.writable=!0),Object.defineProperty(w,A.key,A)}}function b(w,v,d){return v&&y(w.prototype,v),d&&y(w,d),w}var z=function(){function w(v){h(this,w),this.resolveOptions(v),this.initSelection()}return b(w,[{key:"resolveOptions",value:function(){var d=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=d.action,this.container=d.container,this.emitter=d.emitter,this.target=d.target,this.text=d.text,this.trigger=d.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"createFakeElement",value:function(){var d=document.documentElement.getAttribute("dir")==="rtl";this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[d?"right":"left"]="-9999px";var A=window.pageYOffset||document.documentElement.scrollTop;return this.fakeElem.style.top="".concat(A,"px"),this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.fakeElem}},{key:"selectFake",value:function(){var d=this,A=this.createFakeElement();this.fakeHandlerCallback=function(){return d.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.container.appendChild(A),this.selectedText=m()(A),this.copyText(),this.removeFake()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=m()(this.target),this.copyText()}},{key:"copyText",value:function(){var d;try{d=document.execCommand(this.action)}catch(A){d=!1}this.handleResult(d)}},{key:"handleResult",value:function(d){this.emitter.emit(d?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var d=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"copy";if(this._action=d,this._action!=="copy"&&this._action!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(d){if(d!==void 0)if(d&&f(d)==="object"&&d.nodeType===1){if(this.action==="copy"&&d.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(this.action==="cut"&&(d.hasAttribute("readonly")||d.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`);this._target=d}else throw new Error('Invalid "target" value, use a valid Element')},get:function(){return this._target}}]),w}(),P=z;function C(w){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(d){return typeof d}:C=function(d){return d&&typeof Symbol=="function"&&d.constructor===Symbol&&d!==Symbol.prototype?"symbol":typeof d},C(w)}function T(w,v){if(!(w instanceof v))throw new TypeError("Cannot call a class as a function")}function at(w,v){for(var d=0;d<v.length;d++){var A=v[d];A.enumerable=A.enumerable||!1,A.configurable=!0,"value"in A&&(A.writable=!0),Object.defineProperty(w,A.key,A)}}function st(w,v,d){return v&&at(w.prototype,v),d&&at(w,d),w}function Ai(w,v){if(typeof v!="function"&&v!==null)throw new TypeError("Super expression must either be null or a function");w.prototype=Object.create(v&&v.prototype,{constructor:{value:w,writable:!0,configurable:!0}}),v&&Nt(w,v)}function Nt(w,v){return Nt=Object.setPrototypeOf||function(A,I){return A.__proto__=I,A},Nt(w,v)}function Li(w){var v=ki();return function(){var A=ct(w),I;if(v){var X=ct(this).constructor;I=Reflect.construct(A,arguments,X)}else I=A.apply(this,arguments);return Hi(this,I)}}function Hi(w,v){return v&&(C(v)==="object"||typeof v=="function")?v:Ci(w)}function Ci(w){if(w===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return w}function ki(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(w){return!1}}function ct(w){return ct=Object.setPrototypeOf?Object.getPrototypeOf:function(d){return d.__proto__||Object.getPrototypeOf(d)},ct(w)}function Wt(w,v){var d="data-clipboard-".concat(w);if(!!v.hasAttribute(d))return v.getAttribute(d)}var ji=function(w){Ai(d,w);var v=Li(d);function d(A,I){var X;return T(this,d),X=v.call(this),X.resolveOptions(I),X.listenClick(A),X}return st(d,[{key:"resolveOptions",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=C(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var X=this;this.listener=u()(I,"click",function(Ye){return X.onClick(Ye)})}},{key:"onClick",value:function(I){var X=I.delegateTarget||I.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new P({action:this.action(X),target:this.target(X),text:this.text(X),container:this.container,trigger:X,emitter:this})}},{key:"defaultAction",value:function(I){return Wt("action",I)}},{key:"defaultTarget",value:function(I){var X=Wt("target",I);if(X)return document.querySelector(X)}},{key:"defaultText",value:function(I){return Wt("text",I)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],X=typeof I=="string"?[I]:I,Ye=!!document.queryCommandSupported;return X.forEach(function(Ii){Ye=Ye&&!!document.queryCommandSupported(Ii)}),Ye}}]),d}(s()),Fi=ji},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(l,m,f,h,y){var b=u.apply(this,arguments);return l.addEventListener(f,b,y),{destroy:function(){l.removeEventListener(f,b,y)}}}function c(l,m,f,h,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof f=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(b){return s(b,m,f,h,y)}))}function u(l,m,f,h){return function(y){y.delegateTarget=a(y.target,m),y.delegateTarget&&h.call(l,y)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function c(f,h,y){if(!f&&!h&&!y)throw new Error("Missing required arguments");if(!a.string(h))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(f))return u(f,h,y);if(a.nodeList(f))return l(f,h,y);if(a.string(f))return m(f,h,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function u(f,h,y){return f.addEventListener(h,y),{destroy:function(){f.removeEventListener(h,y)}}}function l(f,h,y){return Array.prototype.forEach.call(f,function(b){b.addEventListener(h,y)}),{destroy:function(){Array.prototype.forEach.call(f,function(b){b.removeEventListener(h,y)})}}}function m(f,h,y){return s(document.body,f,h,y)}n.exports=c},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),u=document.createRange();u.selectNodeContents(i),c.removeAllRanges(),c.addRange(u),a=c.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function u(){c.off(i,u),a.apply(s,arguments)}return u._=a,this.on(i,u,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,u=s.length;for(c;c<u;c++)s[c].fn.apply(s[c].ctx,a);return this},off:function(i,a){var s=this.e||(this.e={}),c=s[i],u=[];if(c&&a)for(var l=0,m=c.length;l<m;l++)c[l].fn!==a&&c[l].fn._!==a&&u.push(c[l]);return u.length?s[i]=u:delete s[i],this}},n.exports=o,n.exports.TinyEmitter=o}},t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}return function(){r.n=function(n){var o=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(o,{a:o}),o}}(),function(){r.d=function(n,o){for(var i in o)r.o(o,i)&&!r.o(n,i)&&Object.defineProperty(n,i,{enumerable:!0,get:o[i]})}}(),function(){r.o=function(n,o){return Object.prototype.hasOwnProperty.call(n,o)}}(),r(134)}().default})});var oi=ft((vg,ni)=>{"use strict";var Aa=/["'&<>]/;ni.exports=La;function La(e){var t=""+e,r=Aa.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i<t.length;i++){switch(t.charCodeAt(i)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}a!==i&&(o+=t.substring(a,i)),a=i+1,o+=n}return a!==i?o+t.substring(a,i):o}});var CS=Be(Or());var qr=Be(Qr()),{__extends:K,__assign:qa,__rest:Ka,__decorate:Ja,__param:Ya,__metadata:Ba,__awaiter:Kr,__generator:Jr,__exportStar:Ga,__createBinding:Xa,__values:ce,__read:H,__spread:Za,__spreadArrays:es,__spreadArray:j,__await:ts,__asyncGenerator:rs,__asyncDelegator:ns,__asyncValues:Yr,__makeTemplateObject:os,__importStar:is,__importDefault:as,__classPrivateFieldGet:ss,__classPrivateFieldSet:cs}=qr.default;function S(e){return typeof e=="function"}function ht(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var bt=ht(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription:
+`+r.map(function(n,o){return o+1+") "+n.toString()}).join(`
+  `):"",this.name="UnsubscriptionError",this.errors=r}});function xe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var oe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._teardowns=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(Array.isArray(a))try{for(var s=ce(a),c=s.next();!c.done;c=s.next()){var u=c.value;u.remove(this)}}catch(b){t={error:b}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a==null||a.remove(this);var l=this.initialTeardown;if(S(l))try{l()}catch(b){i=b instanceof bt?b.errors:[b]}var m=this._teardowns;if(m){this._teardowns=null;try{for(var f=ce(m),h=f.next();!h.done;h=f.next()){var y=h.value;try{Br(y)}catch(b){i=i!=null?i:[],b instanceof bt?i=j(j([],H(i)),H(b.errors)):i.push(b)}}}catch(b){n={error:b}}finally{try{h&&!h.done&&(o=f.return)&&o.call(f)}finally{if(n)throw n.error}}}if(i)throw new bt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)Br(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._teardowns=(r=this._teardowns)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&xe(r,t)},e.prototype.remove=function(t){var r=this._teardowns;r&&xe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Kt=oe.EMPTY;function vt(e){return e instanceof oe||e&&"closed"in e&&S(e.remove)&&S(e.add)&&S(e.unsubscribe)}function Br(e){S(e)?e():e.unsubscribe()}var de={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var $e={setTimeout:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=$e.delegate;return((r==null?void 0:r.setTimeout)||setTimeout).apply(void 0,j([],H(e)))},clearTimeout:function(e){var t=$e.delegate;return((t==null?void 0:t.clearTimeout)||clearTimeout)(e)},delegate:void 0};function gt(e){$e.setTimeout(function(){var t=de.onUnhandledError;if(t)t(e);else throw e})}function Z(){}var Gr=function(){return Jt("C",void 0,void 0)}();function Xr(e){return Jt("E",void 0,e)}function Zr(e){return Jt("N",e,void 0)}function Jt(e,t,r){return{kind:e,value:t,error:r}}var Ge=function(e){K(t,e);function t(r){var n=e.call(this)||this;return n.isStopped=!1,r?(n.destination=r,vt(r)&&r.add(n)):n.destination=Ni,n}return t.create=function(r,n,o){return new Yt(r,n,o)},t.prototype.next=function(r){this.isStopped?Bt(Zr(r),this):this._next(r)},t.prototype.error=function(r){this.isStopped?Bt(Xr(r),this):(this.isStopped=!0,this._error(r))},t.prototype.complete=function(){this.isStopped?Bt(Gr,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this),this.destination=null)},t.prototype._next=function(r){this.destination.next(r)},t.prototype._error=function(r){try{this.destination.error(r)}finally{this.unsubscribe()}},t.prototype._complete=function(){try{this.destination.complete()}finally{this.unsubscribe()}},t}(oe);var Yt=function(e){K(t,e);function t(r,n,o){var i=e.call(this)||this,a;if(S(r))a=r;else if(r){a=r.next,n=r.error,o=r.complete;var s;i&&de.useDeprecatedNextContext?(s=Object.create(r),s.unsubscribe=function(){return i.unsubscribe()}):s=r,a=a==null?void 0:a.bind(s),n=n==null?void 0:n.bind(s),o=o==null?void 0:o.bind(s)}return i.destination={next:a?Gt(a,i):Z,error:Gt(n!=null?n:en,i),complete:o?Gt(o,i):Z},i}return t}(Ge);function Gt(e,t){return function(){for(var r=[],n=0;n<arguments.length;n++)r[n]=arguments[n];try{e.apply(void 0,j([],H(r)))}catch(o){if(de.useDeprecatedSynchronousErrorHandling)if(t._syncErrorHack_isSubscribing)t.__syncError=o;else throw o;else gt(o)}}}function en(e){throw e}function Bt(e,t){var r=de.onStoppedNotification;r&&$e.setTimeout(function(){return r(e,t)})}var Ni={closed:!0,next:Z,error:en,complete:Z};var Ee=function(){return typeof Symbol=="function"&&Symbol.observable||"@@observable"}();function ue(e){return e}function tn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Xt(e)}function Xt(e){return e.length===0?ue:e.length===1?e[0]:function(r){return e.reduce(function(n,o){return o(n)},r)}}var M=function(){function e(t){t&&(this._subscribe=t)}return e.prototype.lift=function(t){var r=new e;return r.source=this,r.operator=t,r},e.prototype.subscribe=function(t,r,n){var o=Wi(t)?t:new Yt(t,r,n),i=this,a=i.operator,s=i.source,c=o;if(de.useDeprecatedSynchronousErrorHandling&&(c._syncErrorHack_isSubscribing=!0),o.add(a?a.call(o,s):s||de.useDeprecatedSynchronousErrorHandling?this._subscribe(o):this._trySubscribe(o)),de.useDeprecatedSynchronousErrorHandling)for(c._syncErrorHack_isSubscribing=!1;c;){if(c.__syncError)throw c.__syncError;c=c.destination}return o},e.prototype._trySubscribe=function(t){try{return this._subscribe(t)}catch(r){t.error(r)}},e.prototype.forEach=function(t,r){var n=this;return r=rn(r),new r(function(o,i){var a;a=n.subscribe(function(s){try{t(s)}catch(c){i(c),a==null||a.unsubscribe()}},i,o)})},e.prototype._subscribe=function(t){var r;return(r=this.source)===null||r===void 0?void 0:r.subscribe(t)},e.prototype[Ee]=function(){return this},e.prototype.pipe=function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return t.length?Xt(t)(this):this},e.prototype.toPromise=function(t){var r=this;return t=rn(t),new t(function(n,o){var i;r.subscribe(function(a){return i=a},function(a){return o(a)},function(){return n(i)})})},e.create=function(t){return new e(t)},e}();function rn(e){var t;return(t=e!=null?e:de.Promise)!==null&&t!==void 0?t:Promise}function zi(e){return e&&S(e.next)&&S(e.error)&&S(e.complete)}function Wi(e){return e&&e instanceof Ge||zi(e)&&vt(e)}function Qi(e){return S(e==null?void 0:e.lift)}function g(e){return function(t){if(Qi(t))return t.lift(function(r){try{return e(r,this)}catch(n){this.error(n)}});throw new TypeError("Unable to lift unknown Observable type")}}var x=function(e){K(t,e);function t(r,n,o,i,a){var s=e.call(this,r)||this;return s.onFinalize=a,s._next=n?function(c){try{n(c)}catch(u){r.error(u)}}:e.prototype._next,s._error=o?function(c){try{o(c)}catch(u){r.error(u)}finally{this.unsubscribe()}}:e.prototype._error,s._complete=i?function(){try{i()}catch(c){r.error(c)}finally{this.unsubscribe()}}:e.prototype._complete,s}return t.prototype.unsubscribe=function(){var r,n=this.closed;e.prototype.unsubscribe.call(this),!n&&((r=this.onFinalize)===null||r===void 0||r.call(this))},t}(Ge);var Ve={schedule:function(e){var t=requestAnimationFrame,r=cancelAnimationFrame,n=Ve.delegate;n&&(t=n.requestAnimationFrame,r=n.cancelAnimationFrame);var o=t(function(i){r=void 0,e(i)});return new oe(function(){return r==null?void 0:r(o)})},requestAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ve.delegate;return((r==null?void 0:r.requestAnimationFrame)||requestAnimationFrame).apply(void 0,j([],H(e)))},cancelAnimationFrame:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ve.delegate;return((r==null?void 0:r.cancelAnimationFrame)||cancelAnimationFrame).apply(void 0,j([],H(e)))},delegate:void 0};var nn=ht(function(e){return function(){e(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"}});var _=function(e){K(t,e);function t(){var r=e.call(this)||this;return r.observers=[],r.closed=!1,r.isStopped=!1,r.hasError=!1,r.thrownError=null,r}return t.prototype.lift=function(r){var n=new on(this,this);return n.operator=r,n},t.prototype._throwIfClosed=function(){if(this.closed)throw new nn},t.prototype.next=function(r){var n,o;if(this._throwIfClosed(),!this.isStopped){var i=this.observers.slice();try{for(var a=ce(i),s=a.next();!s.done;s=a.next()){var c=s.value;c.next(r)}}catch(u){n={error:u}}finally{try{s&&!s.done&&(o=a.return)&&o.call(a)}finally{if(n)throw n.error}}}},t.prototype.error=function(r){if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=r;for(var n=this.observers;n.length;)n.shift().error(r)}},t.prototype.complete=function(){if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;for(var r=this.observers;r.length;)r.shift().complete()}},t.prototype.unsubscribe=function(){this.isStopped=this.closed=!0,this.observers=null},t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Kt:(s.push(r),new oe(function(){return xe(n.observers,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new M;return r.source=this,r},t.create=function(r,n){return new on(r,n)},t}(M);var on=function(e){K(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Kt},t}(_);var Xe={now:function(){return(Xe.delegate||Date).now()},delegate:void 0};var yt=function(e){K(t,e);function t(r,n,o){r===void 0&&(r=Infinity),n===void 0&&(n=Infinity),o===void 0&&(o=Xe);var i=e.call(this)||this;return i.bufferSize=r,i.windowTime=n,i.timestampProvider=o,i.buffer=[],i.infiniteTimeWindow=!0,i.infiniteTimeWindow=n===Infinity,i.bufferSize=Math.max(1,r),i.windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n.buffer,a=n.infiniteTimeWindow,s=n.timestampProvider,c=n.windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this.trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this.trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o.infiniteTimeWindow,a=o.buffer,s=a.slice(),c=0;c<s.length&&!r.closed;c+=i?1:2)r.next(s[c]);return this._checkFinalizedStatuses(r),n},t.prototype.trimBuffer=function(){var r=this,n=r.bufferSize,o=r.timestampProvider,i=r.buffer,a=r.infiniteTimeWindow,s=(a?1:2)*n;if(n<Infinity&&s<i.length&&i.splice(0,i.length-s),!a){for(var c=o.now(),u=0,l=1;l<i.length&&i[l]<=c;l+=2)u=l;u&&i.splice(0,u+1)}},t}(_);var an=function(e){K(t,e);function t(r,n){return e.call(this)||this}return t.prototype.schedule=function(r,n){return n===void 0&&(n=0),this},t}(oe);var Ze={setInterval:function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Ze.delegate;return((r==null?void 0:r.setInterval)||setInterval).apply(void 0,j([],H(e)))},clearInterval:function(e){var t=Ze.delegate;return((t==null?void 0:t.clearInterval)||clearInterval)(e)},delegate:void 0};var xt=function(e){K(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o.pending=!1,o}return t.prototype.schedule=function(r,n){if(n===void 0&&(n=0),this.closed)return this;this.state=r;var o=this.id,i=this.scheduler;return o!=null&&(this.id=this.recycleAsyncId(i,o,n)),this.pending=!0,this.delay=n,this.id=this.id||this.requestAsyncId(i,this.id,n),this},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),Ze.setInterval(r.flush.bind(r,this),o)},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&this.delay===o&&this.pending===!1)return n;Ze.clearInterval(n)},t.prototype.execute=function(r,n){if(this.closed)return new Error("executing a cancelled action");this.pending=!1;var o=this._execute(r,n);if(o)return o;this.pending===!1&&this.id!=null&&(this.id=this.recycleAsyncId(this.scheduler,this.id,null))},t.prototype._execute=function(r,n){var o=!1,i;try{this.work(r)}catch(a){o=!0,i=!!a&&a||new Error(a)}if(o)return this.unsubscribe(),i},t.prototype.unsubscribe=function(){if(!this.closed){var r=this,n=r.id,o=r.scheduler,i=o.actions;this.work=this.state=this.scheduler=null,this.pending=!1,xe(i,this),n!=null&&(this.id=this.recycleAsyncId(o,n,null)),this.delay=null,e.prototype.unsubscribe.call(this)}},t}(an);var Zt=function(){function e(t,r){r===void 0&&(r=e.now),this.schedulerActionCtor=t,this.now=r}return e.prototype.schedule=function(t,r,n){return r===void 0&&(r=0),new this.schedulerActionCtor(this,t).schedule(n,r)},e.now=Xe.now,e}();var St=function(e){K(t,e);function t(r,n){n===void 0&&(n=Zt.now);var o=e.call(this,r,n)||this;return o.actions=[],o.active=!1,o.scheduled=void 0,o}return t.prototype.flush=function(r){var n=this.actions;if(this.active){n.push(r);return}var o;this.active=!0;do if(o=r.execute(r.state,r.delay))break;while(r=n.shift());if(this.active=!1,o){for(;r=n.shift();)r.unsubscribe();throw o}},t}(Zt);var et=new St(xt),sn=et;var cn=function(e){K(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r.scheduled||(r.scheduled=Ve.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.length===0&&(Ve.cancelAnimationFrame(n),r.scheduled=void 0)},t}(xt);var un=function(e){K(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this.active=!0,this.scheduled=void 0;var n=this.actions,o,i=-1;r=r||n.shift();var a=n.length;do if(o=r.execute(r.state,r.delay))break;while(++i<a&&(r=n.shift()));if(this.active=!1,o){for(;++i<a&&(r=n.shift());)r.unsubscribe();throw o}},t}(St);var J=new un(cn);var he=new M(function(e){return e.complete()});function De(e,t){return new M(function(r){var n=0;return t.schedule(function(){n===e.length?r.complete():(r.next(e[n++]),r.closed||this.schedule())})})}var Ue=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function wt(e){return S(e==null?void 0:e.then)}function ln(e,t){return new M(function(r){var n=new oe;return n.add(t.schedule(function(){var o=e[Ee]();n.add(o.subscribe({next:function(i){n.add(t.schedule(function(){return r.next(i)}))},error:function(i){n.add(t.schedule(function(){return r.error(i)}))},complete:function(){n.add(t.schedule(function(){return r.complete()}))}}))})),n})}function fn(e,t){return new M(function(r){return t.schedule(function(){return e.then(function(n){r.add(t.schedule(function(){r.next(n),r.add(t.schedule(function(){return r.complete()}))}))},function(n){r.add(t.schedule(function(){return r.error(n)}))})})})}function qi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Et=qi();function pn(e,t,r,n){n===void 0&&(n=0);var o=t.schedule(function(){try{r.call(this)}catch(i){e.error(i)}},n);return e.add(o),o}function mn(e,t){return new M(function(r){var n;return r.add(t.schedule(function(){n=e[Et](),pn(r,t,function(){var o=n.next(),i=o.value,a=o.done;a?r.complete():(r.next(i),this.schedule())})})),function(){return S(n==null?void 0:n.return)&&n.return()}})}function Ot(e){return S(e[Ee])}function Tt(e){return S(e==null?void 0:e[Et])}function dn(e,t){if(!e)throw new Error("Iterable cannot be null");return new M(function(r){var n=new oe;return n.add(t.schedule(function(){var o=e[Symbol.asyncIterator]();n.add(t.schedule(function(){var i=this;o.next().then(function(a){a.done?r.complete():(r.next(a.value),i.schedule())})}))})),n})}function _t(e){return Symbol.asyncIterator&&S(e==null?void 0:e[Symbol.asyncIterator])}function Mt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, Array, AsyncIterable, or Iterable.")}function hn(e,t){if(e!=null){if(Ot(e))return ln(e,t);if(Ue(e))return De(e,t);if(wt(e))return fn(e,t);if(_t(e))return dn(e,t);if(Tt(e))return mn(e,t)}throw Mt(e)}function Se(e,t){return t?hn(e,t):W(e)}function W(e){if(e instanceof M)return e;if(e!=null){if(Ot(e))return Ki(e);if(Ue(e))return er(e);if(wt(e))return Ji(e);if(_t(e))return Bi(e);if(Tt(e))return Yi(e)}throw Mt(e)}function Ki(e){return new M(function(t){var r=e[Ee]();if(S(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function er(e){return new M(function(t){for(var r=0;r<e.length&&!t.closed;r++)t.next(e[r]);t.complete()})}function Ji(e){return new M(function(t){e.then(function(r){t.closed||(t.next(r),t.complete())},function(r){return t.error(r)}).then(null,gt)})}function Yi(e){return new M(function(t){var r,n;try{for(var o=ce(e),i=o.next();!i.done;i=o.next()){var a=i.value;if(t.next(a),t.closed)return}}catch(s){r={error:s}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}t.complete()})}function Bi(e){return new M(function(t){Gi(e,t).catch(function(r){return t.error(r)})})}function Gi(e,t){var r,n,o,i;return Kr(this,void 0,void 0,function(){var a,s;return Jr(this,function(c){switch(c.label){case 0:c.trys.push([0,5,6,11]),r=Yr(e),c.label=1;case 1:return[4,r.next()];case 2:if(n=c.sent(),!!n.done)return[3,4];if(a=n.value,t.next(a),t.closed)return[2];c.label=3;case 3:return[3,1];case 4:return[3,11];case 5:return s=c.sent(),o={error:s},[3,11];case 6:return c.trys.push([6,,9,10]),n&&!n.done&&(i=r.return)?[4,i.call(r)]:[3,8];case 7:c.sent(),c.label=8;case 8:return[3,10];case 9:if(o)throw o.error;return[7];case 10:return[7];case 11:return t.complete(),[2]}})})}function be(e,t){return t?De(e,t):er(e)}function At(e){return e&&S(e.schedule)}function tr(e){return e[e.length-1]}function Oe(e){return S(tr(e))?e.pop():void 0}function fe(e){return At(tr(e))?e.pop():void 0}function Lt(e,t){return typeof tr(e)=="number"?e.pop():t}function F(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e);return r?De(e,r):be(e)}function bn(e){return e instanceof Date&&!isNaN(e)}function p(e,t){return g(function(r,n){var o=0;r.subscribe(new x(n,function(i){n.next(e.call(t,i,o++))}))})}var Xi=Array.isArray;function Zi(e,t){return Xi(t)?e.apply(void 0,j([],H(t))):e(t)}function Ne(e){return p(function(t){return Zi(e,t)})}function Y(e,t){return t===void 0&&(t=0),g(function(r,n){r.subscribe(new x(n,function(o){return n.add(e.schedule(function(){return n.next(o)},t))},function(o){return n.add(e.schedule(function(){return n.error(o)},t))},function(){return n.add(e.schedule(function(){return n.complete()},t))}))})}var ea=Array.isArray,ta=Object.getPrototypeOf,ra=Object.prototype,na=Object.keys;function vn(e){if(e.length===1){var t=e[0];if(ea(t))return{args:t,keys:null};if(oa(t)){var r=na(t);return{args:r.map(function(n){return t[n]}),keys:r}}}return{args:e,keys:null}}function oa(e){return e&&typeof e=="object"&&ta(e)===ra}function gn(e,t){return e.reduce(function(r,n,o){return r[n]=t[o],r},{})}function B(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Oe(e),o=vn(e),i=o.args,a=o.keys;if(i.length===0)return Se([],r);var s=new M(rr(i,r,a?function(c){return gn(a,c)}:ue));return n?s.pipe(Ne(n)):s}function rr(e,t,r){return r===void 0&&(r=ue),function(n){yn(t,function(){for(var o=e.length,i=new Array(o),a=o,s=o,c=function(l){yn(t,function(){var m=Se(e[l],t),f=!1;m.subscribe(new x(n,function(h){i[l]=h,f||(f=!0,s--),s||n.next(r(i.slice()))},void 0,function(){--a||n.complete()}))},n)},u=0;u<o;u++)c(u)},n)}}function yn(e,t,r){e?r.add(e.schedule(t)):t()}function xn(e,t,r,n,o,i,a,s){var c=[],u=0,l=0,m=!1,f=function(){m&&!c.length&&!u&&t.complete()},h=function(b){return u<n?y(b):c.push(b)},y=function(b){i&&t.next(b),u++;var z=!1;W(r(b,l++)).subscribe(new x(t,function(P){o==null||o(P),i?h(P):t.next(P)},void 0,function(){z=!0},function(){if(z)try{u--;for(var P=function(){var C=c.shift();a?t.add(a.schedule(function(){return y(C)})):y(C)};c.length&&u<n;)P();f()}catch(C){t.error(C)}}))};return e.subscribe(new x(t,h,void 0,function(){m=!0,f()})),function(){s==null||s()}}function te(e,t,r){return r===void 0&&(r=Infinity),S(t)?te(function(n,o){return p(function(i,a){return t(n,i,o,a)})(W(e(n,o)))},r):(typeof t=="number"&&(r=t),g(function(n,o){return xn(n,o,e,r)}))}function We(e){return e===void 0&&(e=Infinity),te(ue,e)}function Sn(){return We(1)}function tt(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Sn()(be(e,fe(e)))}function Te(e){return new M(function(t){W(e()).subscribe(t)})}var ia=["addListener","removeListener"],aa=["addEventListener","removeEventListener"],sa=["on","off"];function O(e,t,r,n){if(S(r)&&(n=r,r=void 0),n)return O(e,t,r).pipe(Ne(n));var o=H(la(e)?aa.map(function(s){return function(c){return e[s](t,c,r)}}):ca(e)?ia.map(wn(e,t)):ua(e)?sa.map(wn(e,t)):[],2),i=o[0],a=o[1];if(!i&&Ue(e))return te(function(s){return O(s,t,r)})(be(e));if(!i)throw new TypeError("Invalid event target");return new M(function(s){var c=function(){for(var u=[],l=0;l<arguments.length;l++)u[l]=arguments[l];return s.next(1<u.length?u:u[0])};return i(c),function(){return a(c)}})}function wn(e,t){return function(r){return function(n){return e[r](t,n)}}}function ca(e){return S(e.addListener)&&S(e.removeListener)}function ua(e){return S(e.on)&&S(e.off)}function la(e){return S(e.addEventListener)&&S(e.removeEventListener)}function En(e,t,r){e===void 0&&(e=0),r===void 0&&(r=sn);var n=-1;return t!=null&&(At(t)?r=t:n=t),new M(function(o){var i=bn(e)?+e-r.now():e;i<0&&(i=0);var a=0;return r.schedule(function(){o.closed||(o.next(a++),0<=n?this.schedule(void 0,n):o.complete())},i)})}var fa=Array.isArray;function _e(e){return e.length===1&&fa(e[0])?e[0]:e}function R(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Lt(e,Infinity),o=_e(e);return o.length?o.length===1?W(o[0]):We(n)(be(o,r)):he}var ee=new M(Z);function L(e,t){return g(function(r,n){var o=0;r.subscribe(new x(n,function(i){return e.call(t,i,o++)&&n.next(i)}))})}function Ht(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e),n=_e(e);return n.length?new M(function(o){var i=n.map(function(){return[]}),a=n.map(function(){return!1});o.add(function(){i=a=null});for(var s=function(u){W(n[u]).subscribe(new x(o,function(l){if(i[u].push(l),i.every(function(f){return f.length})){var m=i.map(function(f){return f.shift()});o.next(r?r.apply(void 0,j([],H(m))):m),i.some(function(f,h){return!f.length&&a[h]})&&o.complete()}},void 0,function(){a[u]=!0,!i[u].length&&o.complete()}))},c=0;!o.closed&&c<n.length;c++)s(c);return function(){i=a=null}}):he}function ve(e,t){return t===void 0&&(t=null),t=t!=null?t:e,g(function(r,n){var o=[],i=0;r.subscribe(new x(n,function(a){var s,c,u,l,m=null;i++%t==0&&o.push([]);try{for(var f=ce(o),h=f.next();!h.done;h=f.next()){var y=h.value;y.push(a),e<=y.length&&(m=m!=null?m:[],m.push(y))}}catch(P){s={error:P}}finally{try{h&&!h.done&&(c=f.return)&&c.call(f)}finally{if(s)throw s.error}}if(m)try{for(var b=ce(m),z=b.next();!z.done;z=b.next()){var y=z.value;xe(o,y),n.next(y)}}catch(P){u={error:P}}finally{try{z&&!z.done&&(l=b.return)&&l.call(b)}finally{if(u)throw u.error}}},void 0,function(){var a,s;try{for(var c=ce(o),u=c.next();!u.done;u=c.next()){var l=u.value;n.next(l)}}catch(m){a={error:m}}finally{try{u&&!u.done&&(s=c.return)&&s.call(c)}finally{if(a)throw a.error}}n.complete()},function(){o=null}))})}function rt(e){return g(function(t,r){var n=null,o=!1,i;n=t.subscribe(new x(r,void 0,function(a){i=W(e(a,rt(e)(t))),n?(n.unsubscribe(),n=null,i.subscribe(r)):o=!0})),o&&(n.unsubscribe(),n=null,i.subscribe(r))})}function On(e,t,r,n,o){return function(i,a){var s=r,c=t,u=0;i.subscribe(new x(a,function(l){var m=u++;c=s?e(c,l,m):(s=!0,l),n&&a.next(c)},void 0,o&&function(){s&&a.next(c),a.complete()}))}}function nr(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e);return r?tn(nr.apply(void 0,j([],H(e))),Ne(r)):g(function(n,o){rr(j([n],H(_e(e))))(o)})}function or(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return nr.apply(void 0,j([],H(e)))}function Tn(e,t){return S(t)?te(e,t,1):te(e,1)}function _n(e,t){return t===void 0&&(t=et),g(function(r,n){var o=null,i=null,a=null,s=function(){if(o){o.unsubscribe(),o=null;var u=i;i=null,n.next(u)}};function c(){var u=a+e,l=t.now();if(l<u){o=this.schedule(void 0,u-l);return}s()}r.subscribe(new x(n,function(u){i=u,a=t.now(),o||(o=t.schedule(c,e))},void 0,function(){s(),n.complete()},function(){i=o=null}))})}function ze(e){return g(function(t,r){var n=!1;t.subscribe(new x(r,function(o){n=!0,r.next(o)},void 0,function(){n||r.next(e),r.complete()}))})}function nt(e){return e<=0?function(){return he}:g(function(t,r){var n=0;t.subscribe(new x(r,function(o){++n<=e&&(r.next(o),e<=n&&r.complete())}))})}function Mn(){return g(function(e,t){e.subscribe(new x(t,Z))})}function ne(e){return g(function(t,r){t.subscribe(new x(r,function(){return r.next(e)}))})}function ir(e,t){return t?function(r){return tt(t.pipe(nt(1),Mn()),r.pipe(ir(e)))}:te(function(r,n){return e(r,n).pipe(nt(1),ne(r))})}function Me(e,t){t===void 0&&(t=et);var r=En(e,t);return ir(function(){return r})}function Q(e,t){return t===void 0&&(t=ue),e=e!=null?e:pa,g(function(r,n){var o,i=!0;r.subscribe(new x(n,function(a){var s=t(a);(i||!e(o,s))&&(i=!1,o=s,n.next(a))}))})}function pa(e,t){return e===t}function N(e,t){return Q(function(r,n){return t?t(r[e],n[e]):r[e]===n[e]})}function V(e){return g(function(t,r){t.subscribe(r),r.add(e)})}function An(e){return e<=0?function(){return he}:g(function(t,r){var n=[];t.subscribe(new x(r,function(o){n.push(o),e<n.length&&n.shift()},void 0,function(){var o,i;try{for(var a=ce(n),s=a.next();!s.done;s=a.next()){var c=s.value;r.next(c)}}catch(u){o={error:u}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}r.complete()},function(){n=null}))})}function Ln(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e),n=Lt(e,Infinity);return e=_e(e),g(function(o,i){We(n)(be(j([o],H(e)),r)).subscribe(i)})}function Ct(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Ln.apply(void 0,j([],H(e)))}function ot(e){return g(function(t,r){var n=!1,o=null;t.subscribe(new x(r,function(a){n=!0,o=a}));var i=function(){if(n){n=!1;var a=o;o=null,r.next(a)}};e.subscribe(new x(r,i,void 0,Z))})}function Hn(e,t){return g(On(e,t,arguments.length>=2,!0))}function ie(e){e=e||{};var t=e.connector,r=t===void 0?function(){return new _}:t,n=e.resetOnComplete,o=n===void 0?!0:n,i=e.resetOnError,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s,u=null,l=null,m=0,f=!1,h=!1,y=function(){u=l=null,f=h=!1};return g(function(b,z){return m++,l=l!=null?l:r(),l.subscribe(z),u||(u=Se(b).subscribe({next:function(P){return l.next(P)},error:function(P){h=!0;var C=l;a&&y(),C.error(P)},complete:function(){f=!0;var P=l;o&&y(),P.complete()}})),function(){if(m--,c&&!m&&!h&&!f){var P=u;y(),P==null||P.unsubscribe()}}})}function re(e,t,r){var n,o,i,a=!1;return e&&typeof e=="object"?(i=(n=e.bufferSize)!==null&&n!==void 0?n:Infinity,t=(o=e.windowTime)!==null&&o!==void 0?o:Infinity,a=!!e.refCount,r=e.scheduler):i=e!=null?e:Infinity,ie({connector:function(){return new yt(i,t,r)},resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:a})}function ar(e){return L(function(t,r){return e<=r})}function Cn(e){return g(function(t,r){var n=!1,o=new x(r,function(){o==null||o.unsubscribe(),n=!0},void 0,Z);W(e).subscribe(o),t.subscribe(new x(r,function(i){return n&&r.next(i)}))})}function D(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=fe(e);return g(function(n,o){(r?tt(e,n,r):tt(e,n)).subscribe(o)})}function E(e,t){return g(function(r,n){var o=null,i=0,a=!1,s=function(){return a&&!o&&n.complete()};r.subscribe(new x(n,function(c){o==null||o.unsubscribe();var u=0,l=i++;W(e(c,l)).subscribe(o=new x(n,function(m){return n.next(t?t(c,m,l,u++):m)},void 0,function(){o=null,s()}))},void 0,function(){a=!0,s()}))})}function kn(e,t){return S(t)?E(function(){return e},t):E(function(){return e})}function jn(e){return g(function(t,r){W(e).subscribe(new x(r,function(){return r.complete()},void 0,Z)),!r.closed&&t.subscribe(r)})}function Fn(e,t){return t===void 0&&(t=!1),g(function(r,n){var o=0;r.subscribe(new x(n,function(i){var a=e(i,o++);(a||t)&&n.next(i),!a&&n.complete()}))})}function k(e,t,r){var n=S(e)||t||r?{next:e,error:t,complete:r}:e;return n?g(function(o,i){o.subscribe(new x(i,function(a){var s;(s=n.next)===null||s===void 0||s.call(n,a),i.next(a)},function(a){var s;(s=n.error)===null||s===void 0||s.call(n,a),i.error(a)},function(){var a;(a=n.complete)===null||a===void 0||a.call(n),i.complete()}))}):ue}var ma={leading:!0,trailing:!1};function In(e,t){var r=t===void 0?ma:t,n=r.leading,o=r.trailing;return g(function(i,a){var s=!1,c=null,u=null,l=!1,m=function(){u==null||u.unsubscribe(),u=null,o&&(y(),l&&a.complete())},f=function(){u=null,l&&a.complete()},h=function(b){return u=W(e(b)).subscribe(new x(a,m,void 0,f))},y=function(){if(s){s=!1;var b=c;c=null,a.next(b),!l&&h(b)}};i.subscribe(new x(a,function(b){s=!0,c=b,!(u&&!u.closed)&&(n?y():h(b))},void 0,function(){l=!0,!(o&&s&&u&&!u.closed)&&a.complete()}))})}function ge(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];var r=Oe(e);return g(function(n,o){for(var i=e.length,a=new Array(i),s=e.map(function(){return!1}),c=!1,u=function(m){W(e[m]).subscribe(new x(o,function(f){a[m]=f,!c&&!s[m]&&(s[m]=!0,(c=s.every(ue))&&(s=null))},void 0,Z))},l=0;l<i;l++)u(l);n.subscribe(new x(o,function(m){if(c){var f=j([m],H(a));o.next(r?r.apply(void 0,j([],H(f))):f)}}))})}function Rn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return g(function(r,n){Ht.apply(void 0,j([r],H(e))).subscribe(n)})}function Pn(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];return Rn.apply(void 0,j([],H(e)))}function $n(){let e=new yt;return O(document,"DOMContentLoaded").pipe(ne(document)).subscribe(e),e}function ae(e,t=document){return t.querySelector(e)||void 0}function pe(e,t=document){let r=ae(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ke(){return document.activeElement instanceof HTMLElement?document.activeElement:void 0}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function Qe(e){return document.createElement(e)}function je(e,...t){e.replaceWith(...t)}function Ae(e,t=!0){t?e.focus():e.blur()}function Vn(e){return R(O(e,"focus"),O(e,"blur")).pipe(p(({type:t})=>t==="focus"),D(e===ke()))}var Dn=new _,da=Te(()=>F(new ResizeObserver(e=>{for(let t of e)Dn.next(t)}))).pipe(E(e=>ee.pipe(D(e)).pipe(V(()=>e.disconnect()))),re(1));function Le(e){return{width:e.offsetWidth,height:e.offsetHeight}}function kt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function He(e){return da.pipe(k(t=>t.observe(e)),E(t=>Dn.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),p(()=>Le(e)))),D(Le(e)))}function Un(e){return{x:e.scrollLeft,y:e.scrollTop}}function ha(e){return R(O(e,"scroll"),O(window,"resize")).pipe(p(()=>Un(e)),D(Un(e)))}function Nn(e,t=16){return ha(e).pipe(p(({y:r})=>{let n=Le(e),o=kt(e);return r>=o.height-n.height-t}),Q())}function Wn(e){if(e instanceof HTMLInputElement)e.select();else throw new Error("Not implemented")}var jt={drawer:pe("[data-md-toggle=drawer]"),search:pe("[data-md-toggle=search]")};function zn(e){return jt[e].checked}function Fe(e,t){jt[e].checked!==t&&jt[e].click()}function Ft(e){let t=jt[e];return O(t,"change").pipe(p(()=>t.checked),D(t.checked))}function ba(e){switch(e.tagName){case"INPUT":case"SELECT":case"TEXTAREA":return!0;default:return e.isContentEditable}}function Qn(){return O(window,"keydown").pipe(L(e=>!(e.metaKey||e.ctrlKey)),p(e=>({mode:zn("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),L(({mode:e})=>{if(e==="global"){let t=ke();if(typeof t!="undefined")return!ba(t)}return!0}),ie())}function qn(){return new URL(location.href)}function Kn(e){location.href=e.href}function Jn(){return new _}function Yn(){return location.hash.substring(1)}function Bn(e){let t=Qe("a");t.href=e,t.addEventListener("click",r=>r.stopPropagation()),t.click()}function va(){return O(window,"hashchange").pipe(p(Yn),D(Yn()),L(e=>e.length>0),ie())}function Gn(){return va().pipe(E(e=>F(ae(`[id="${e}"]`))))}function qe(e){let t=matchMedia(e);return O(t,"change").pipe(p(r=>r.matches),D(t.matches))}function Xn(){return R(qe("print").pipe(L(Boolean)),O(window,"beforeprint")).pipe(ne(void 0))}function sr(e,t){return e.pipe(E(r=>r?t():ee))}function It(e,t={credentials:"same-origin"}){return Se(fetch(`${e}`,t)).pipe(L(r=>r.status===200))}function ye(e,t){return It(e,t).pipe(E(r=>r.json()),re(1))}function Zn(e,t){let r=new DOMParser;return It(e,t).pipe(E(n=>n.text()),p(n=>r.parseFromString(n,"text/xml")),re(1))}function eo(){return{x:Math.max(0,pageXOffset),y:Math.max(0,pageYOffset)}}function cr({x:e,y:t}){window.scrollTo(e||0,t||0)}function to(){return R(O(window,"scroll",{passive:!0}),O(window,"resize",{passive:!0})).pipe(p(eo),D(eo()))}function ro(){return{width:innerWidth,height:innerHeight}}function no(){return O(window,"resize",{passive:!0}).pipe(p(ro),D(ro()))}function oo(){return B([to(),no()]).pipe(p(([e,t])=>({offset:e,size:t})),re(1))}function Rt(e,{viewport$:t,header$:r}){let n=t.pipe(N("size")),o=B([n,r]).pipe(p(()=>({x:e.offsetLeft,y:e.offsetTop})));return B([r,t,o]).pipe(p(([{height:i},{offset:a,size:s},{x:c,y:u}])=>({offset:{x:a.x-c,y:a.y-u+i},size:s})))}function io(e,{tx$:t}){let r=O(e,"message").pipe(p(({data:n})=>n));return t.pipe(In(()=>r,{leading:!0,trailing:!0}),k(n=>e.postMessage(n)),kn(r),ie())}var ga=pe("#__config"),Ke=JSON.parse(ga.textContent);Ke.base=new URL(Ke.base,qn()).toString().replace(/\/$/,"");function se(){return Ke}function Pt(e){return Ke.features.includes(e)}function G(e,t){return typeof t!="undefined"?Ke.translations[e].replace("#",t.toString()):Ke.translations[e]}function Ce(e,t=document){return pe(`[data-md-component=${e}]`,t)}function me(e,t=document){return q(`[data-md-component=${e}]`,t)}var Wo=Be(lr());function ao(e,t=0){e.setAttribute("tabindex",t.toString())}function so(e){e.removeAttribute("tabindex")}function co(e,t){e.setAttribute("data-md-state","lock"),e.style.top=`-${t}px`}function uo(e){let t=-1*parseInt(e.style.top,10);e.removeAttribute("data-md-state"),e.style.top="",t&&window.scrollTo(0,t)}function lo(e,t){e.setAttribute("data-md-state",t)}function fo(e){e.removeAttribute("data-md-state")}function po(e,t){e.classList.toggle("md-nav__link--active",t)}function mo(e){e.classList.remove("md-nav__link--active")}function ho(e,t){e.firstElementChild.innerHTML=t}function bo(e,t){e.setAttribute("data-md-state",t)}function vo(e){e.removeAttribute("data-md-state")}function go(e,t){e.setAttribute("data-md-state",t)}function yo(e){e.removeAttribute("data-md-state")}function xo(e,t){e.setAttribute("data-md-state",t)}function So(e){e.removeAttribute("data-md-state")}function wo(e,t){e.placeholder=t}function Eo(e){e.placeholder=G("search.placeholder")}function Oo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oo(e,r)}function U(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="boolean"?n.setAttribute(o,t[o]):t[o]&&n.setAttribute(o,"");for(let o of r)Oo(n,o);return n}function To(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function $t(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _o(e,t){switch(t){case 0:e.textContent=G("search.result.none");break;case 1:e.textContent=G("search.result.one");break;default:e.textContent=G("search.result.other",$t(t))}}function Mo(e){e.textContent=G("search.result.placeholder")}function Ao(e,t){e.appendChild(t)}function Lo(e){e.innerHTML=""}function Ho(e,t){e.style.top=`${t}px`}function Co(e){e.style.top=""}function ko(e,t){let r=e.firstElementChild;r.style.height=`${t-2*r.offsetTop}px`}function jo(e){let t=e.firstElementChild;t.style.height=""}function Fo(e,t){e.lastElementChild.appendChild(t)}function Io(e,t){e.lastElementChild.setAttribute("data-md-state",t)}function Ro(e,t){e.setAttribute("data-md-state",t)}function fr(e){e.removeAttribute("data-md-state")}function Po(e,t){e.setAttribute("data-md-state",t)}function pr(e){e.removeAttribute("data-md-state")}function $o(e){return U("button",{class:"md-clipboard md-icon",title:G("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ie;(function(e){e[e.TEASER=1]="TEASER",e[e.PARENT=2]="PARENT"})(Ie||(Ie={}));function mr(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).map(a=>[U("del",null,a)," "]).flat().slice(0,-1),i=e.location;return U("a",{href:i,class:"md-search-result__link",tabIndex:-1},U("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&U("div",{class:"md-search-result__icon md-icon"}),U("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&U("p",{class:"md-search-result__teaser"},To(e.text,320)),n>0&&o.length>0&&U("p",{class:"md-search-result__terms"},G("search.result.term.missing"),": ",o)))}function Vo(e){let t=e[0].score,r=[...e],n=r.findIndex(u=>!u.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(u=>u.score<t);i===-1&&(i=r.length);let a=r.slice(0,i),s=r.slice(i),c=[mr(o,2|+(!n&&i===0)),...a.map(u=>mr(u,1)),...s.length?[U("details",{class:"md-search-result__more"},U("summary",{tabIndex:-1},s.length>0&&s.length===1?G("search.result.more.one"):G("search.result.more.other",s.length)),s.map(u=>mr(u,1)))]:[]];return U("li",{class:"md-search-result__item"},c)}function Do(e){return U("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>U("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?$t(r):r)))}function Uo(e){return U("div",{class:"md-typeset__scrollwrap"},U("div",{class:"md-typeset__table"},e))}function ya(e){let t=se(),r=new URL(`${e.version}/`,t.base);return U("li",{class:"md-version__item"},U("a",{href:r.toString(),class:"md-version__link"},e.title))}function No(e){let t=se(),[,r]=t.base.match(/([^/]+)\/?$/),n=e.find(({version:o,aliases:i})=>o===r||i.includes(r))||e[0];return U("div",{class:"md-version"},U("span",{class:"md-version__current"},n.title),U("ul",{class:"md-version__list"},e.map(ya)))}var xa=0;function Sa(e,{viewport$:t}){let r=F(e).pipe(E(n=>{let o=n.closest("[data-tabs]");return o instanceof HTMLElement?R(...q("input",o).map(i=>O(i,"change"))):ee}));return R(t.pipe(N("size")),r).pipe(p(()=>{let n=Le(e);return{scroll:kt(e).width>n.width}}),N("scroll"))}function zo(e,t){let r=new _;if(r.pipe(ge(qe("(hover)"))).subscribe(([{scroll:n},o])=>{n&&o?ao(e):so(e)}),Wo.default.isSupported()){let n=e.closest("pre");n.id=`__code_${xa++}`,n.insertBefore($o(n.id),e)}return Sa(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function wa(e,{target$:t,print$:r}){return t.pipe(p(n=>n.closest("details:not([open])")),L(n=>e===n),Ct(r),ne(e))}function Qo(e,t){let r=new _;return r.subscribe(()=>{e.setAttribute("open",""),e.scrollIntoView()}),wa(e,t).pipe(k(r),V(()=>r.complete()),ne({ref:e}))}var qo=Qe("table");function Ko(e){return je(e,qo),je(qo,Uo(e)),F({ref:e})}function Jo(e,{target$:t,viewport$:r,print$:n}){return R(...q("pre > code",e).map(o=>zo(o,{viewport$:r})),...q("table:not([class])",e).map(o=>Ko(o)),...q("details",e).map(o=>Qo(o,{target$:t,print$:n})))}function Ea(e,{alert$:t}){return t.pipe(E(r=>R(F(!0),F(!1).pipe(Me(2e3))).pipe(p(n=>({message:r,open:n})))))}function Yo(e,t){let r=new _;return r.pipe(Y(J)).subscribe(({message:n,open:o})=>{ho(e,n),o?bo(e,"open"):vo(e)}),Ea(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Oa({viewport$:e}){if(!Pt("header.autohide"))return F(!1);let t=e.pipe(p(({offset:{y:o}})=>o),ve(2,1),p(([o,i])=>[o<i,i]),N(0)),r=B([e,t]).pipe(L(([{offset:o},[,i]])=>Math.abs(i-o.y)>100),p(([,[o]])=>o),Q()),n=Ft("search");return B([e,n]).pipe(p(([{offset:o},i])=>o.y>400&&!i),Q(),E(o=>o?r:F(!1)),D(!1))}function Bo(e,t){return Te(()=>{let r=getComputedStyle(e);return F(r.position==="sticky"||r.position==="-webkit-sticky")}).pipe(or(He(e),Oa(t)),p(([r,{height:n},o])=>({height:r?n:0,sticky:r,hidden:o})),Q((r,n)=>r.sticky===n.sticky&&r.height===n.height&&r.hidden===n.hidden),re(1))}function Go(e,{header$:t,main$:r}){let n=new _;return n.pipe(N("active"),or(t),Y(J)).subscribe(([{active:o},{hidden:i}])=>{o?go(e,i?"hidden":"shadow"):yo(e)}),r.subscribe(o=>n.next(o)),t.pipe(p(o=>$({ref:e},o)))}function Ta(e,{viewport$:t,header$:r}){return Rt(e,{header$:r,viewport$:t}).pipe(p(({offset:{y:n}})=>{let{height:o}=Le(e);return{active:n>=o}}),N("active"))}function Xo(e,t){let r=new _;r.pipe(Y(J)).subscribe(({active:o})=>{o?xo(e,"active"):So(e)});let n=ae("article h1");return typeof n=="undefined"?ee:Ta(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function Zo(e,{viewport$:t,header$:r}){let n=r.pipe(p(({height:i})=>i),Q()),o=n.pipe(E(()=>He(e).pipe(p(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),N("bottom"))));return B([n,o,t]).pipe(p(([i,{top:a,bottom:s},{offset:{y:c},size:{height:u}}])=>(u=Math.max(0,u-Math.max(0,a-c,i)-Math.max(0,u+c-s)),{offset:a-i,height:u,active:a-i<=c})),Q((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function _a(e){let t=localStorage.getItem(__prefix("__palette")),r=JSON.parse(t)||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},n=F(...e).pipe(te(o=>O(o,"change").pipe(ne(o))),D(e[Math.max(0,r.index)]),p(o=>({index:e.indexOf(o),color:{scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),re(1));return n.subscribe(o=>{localStorage.setItem(__prefix("__palette"),JSON.stringify(o))}),n}function ei(e){let t=new _;t.subscribe(n=>{for(let[o,i]of Object.entries(n.color))typeof i=="string"&&document.body.setAttribute(`data-md-color-${o}`,i);for(let o=0;o<r.length;o++){let i=r[o].nextElementSibling;i.hidden=n.index!==o}});let r=q("input",e);return _a(r).pipe(k(t),V(()=>t.complete()),p(n=>$({ref:e},n)))}var dr=Be(lr());function ti({alert$:e}){dr.default.isSupported()&&new M(t=>{new dr.default("[data-clipboard-target], [data-clipboard-text]").on("success",r=>t.next(r))}).subscribe(()=>e.next(G("clipboard.copied")))}function Ma(e){if(e.length<2)return e;let[t,r]=e.sort((i,a)=>i.length-a.length).map(i=>i.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;let o=se();return e.map(i=>i.replace(t.slice(0,n),`${o.base}/`))}function ri({document$:e,location$:t,viewport$:r}){let n=se();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",O(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=ae("link[rel=icon]");typeof o!="undefined"&&(o.href=o.href);let i=Zn(`${n.base}/sitemap.xml`).pipe(p(u=>Ma(q("loc",u).map(l=>l.textContent))),E(u=>O(document.body,"click").pipe(L(l=>!l.metaKey&&!l.ctrlKey),E(l=>{if(l.target instanceof Element){let m=l.target.closest("a");if(m&&!m.target&&u.includes(m.href))return l.preventDefault(),F({url:new URL(m.href)})}return ee}))),ie()),a=O(window,"popstate").pipe(L(u=>u.state!==null),p(u=>({url:new URL(location.href),offset:u.state})),ie());R(i,a).pipe(Q((u,l)=>u.url.href===l.url.href),p(({url:u})=>u)).subscribe(t);let s=t.pipe(N("pathname"),E(u=>It(u.href).pipe(rt(()=>(Kn(u),ee)))),ie());i.pipe(ot(s)).subscribe(({url:u})=>{history.pushState({},"",`${u}`)});let c=new DOMParser;s.pipe(E(u=>u.text()),p(u=>c.parseFromString(u,"text/html"))).subscribe(e),R(i,a).pipe(ot(e)).subscribe(({url:u,offset:l})=>{u.hash&&!l?Bn(u.hash):cr(l||{y:0})}),e.pipe(ar(1)).subscribe(u=>{for(let l of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=logo], .md-logo","[data-md-component=skip]"]){let m=ae(l),f=ae(l,u);typeof m!="undefined"&&typeof f!="undefined"&&je(m,f)}}),e.pipe(ar(1),p(()=>Ce("container")),E(u=>F(...q("script",u))),Tn(u=>{let l=Qe("script");if(u.src){for(let m of u.getAttributeNames())l.setAttribute(m,u.getAttribute(m));return je(u,l),new M(m=>{l.onload=()=>m.complete()})}else return l.textContent=u.textContent,je(u,l),he})).subscribe(),r.pipe(Cn(i),_n(250),N("offset")).subscribe(({offset:u})=>{history.replaceState(u,"")}),R(i,a).pipe(ve(2,1),L(([u,l])=>u.url.pathname===l.url.pathname),p(([,u])=>u)).subscribe(({offset:u})=>{cr(u||{y:0})})}var Ha=Be(oi());function ii(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}var we;(function(e){e[e.SETUP=0]="SETUP",e[e.READY=1]="READY",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"})(we||(we={}));function ai(e){return e.type===1}function si(e){return e.type===2}function Vt(e){return e.type===3}function Ca({config:e,docs:t,index:r}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[G("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=G("search.config.separator"));let n=G("search.config.pipeline").split(/\s*,\s*/).filter(Boolean);return{config:e,docs:t,index:r,pipeline:n}}function ci(e,t){let r=se(),n=new Worker(e),o=new _,i=io(n,{tx$:o}).pipe(p(a=>{if(Vt(a))for(let s of a.data)for(let c of s)c.location=`${r.base}/${c.location}`;return a}),ie());return Se(t).pipe(p(a=>({type:we.SETUP,data:Ca(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function ui(){let e=se();ye(new URL("versions.json",e.base)).subscribe(t=>{pe(".md-header__topic").appendChild(No(t))})}function ka(e){let t=(__search==null?void 0:__search.transform)||ii,r=Vn(e),n=R(O(e,"keyup"),O(e,"focus").pipe(Me(1))).pipe(p(()=>t(e.value)),Q());return B([n,r]).pipe(p(([o,i])=>({value:o,focus:i})))}function li(e,{tx$:t}){let r=new _;return r.pipe(N("value"),p(({value:n})=>({type:we.QUERY,data:n}))).subscribe(t.next.bind(t)),r.pipe(N("focus")).subscribe(({focus:n})=>{n?(Fe("search",n),wo(e,"")):Eo(e)}),O(e.form,"reset").pipe(jn(r.pipe(An(1)))).subscribe(()=>Ae(e)),ka(e).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function fi(e,{rx$:t},{query$:r}){let n=new _,o=Nn(e.parentElement).pipe(L(Boolean)),i=pe(":scope > :first-child",e);n.pipe(Y(J),ge(r)).subscribe(([{data:c},{value:u}])=>{u?_o(i,c.length):Mo(i)});let a=pe(":scope > :last-child",e);return n.pipe(Y(J),k(()=>Lo(a)),E(({data:c})=>R(F(...c.slice(0,10)),F(...c.slice(10)).pipe(ve(4),Pn(o),E(([u])=>F(...u)))))).subscribe(c=>{Ao(a,Vo(c))}),t.pipe(L(Vt),p(({data:c})=>({data:c})),D({data:[]})).pipe(k(n),V(()=>n.complete()),p(c=>$({ref:e},c)))}function pi(e,{index$:t,keyboard$:r}){let n=se(),o=ci(n.search,t),i=Ce("search-query",e),a=Ce("search-result",e),{tx$:s,rx$:c}=o;s.pipe(L(si),ot(c.pipe(L(ai))),nt(1)).subscribe(s.next.bind(s)),r.pipe(L(({mode:l})=>l==="search")).subscribe(l=>{let m=ke();switch(l.type){case"Enter":m===i&&l.claim();break;case"Escape":case"Tab":Fe("search",!1),Ae(i,!1);break;case"ArrowUp":case"ArrowDown":if(typeof m=="undefined")Ae(i);else{let f=[i,...q(":not(details) > [href], summary, details[open] [href]",a)],h=Math.max(0,(Math.max(0,f.indexOf(m))+f.length+(l.type==="ArrowUp"?-1:1))%f.length);Ae(f[h])}l.claim();break;default:i!==ke()&&Ae(i)}}),r.pipe(L(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":Ae(i),Wn(i),l.claim();break}});let u=li(i,o);return R(u,fi(a,o,{query$:u}))}function ja(e,{viewport$:t,main$:r}){let n=e.parentElement.offsetTop-e.parentElement.parentElement.offsetTop;return B([r,t]).pipe(p(([{offset:o,height:i},{offset:{y:a}}])=>(i=i+Math.min(n,Math.max(0,a-o))-n,{height:i,locked:a>=o+n})),Q((o,i)=>o.height===i.height&&o.locked===i.locked))}function hr(e,n){var{header$:t}=n,r=wr(n,["header$"]);let o=new _;return o.pipe(Y(J),ge(t)).subscribe({next([{height:i},{height:a}]){ko(e,i),Ho(e,a)},complete(){Co(e),jo(e)}}),ja(e,r).pipe(k(o),V(()=>o.complete()),p(i=>$({ref:e},i)))}function mi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ht(ye(`${r}/releases/latest`).pipe(p(n=>({version:n.tag_name})),ze({})),ye(r).pipe(p(n=>({stars:n.stargazers_count,forks:n.forks_count})),ze({}))).pipe(p(([n,o])=>$($({},n),o)))}else{let r=`https://api.github.com/repos/${e}`;return ye(r).pipe(p(n=>({repositories:n.public_repos})),ze({}))}}function di(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return ye(r).pipe(p(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ze({}))}function hi(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return mi(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return di(o,i);default:return ee}}var Fa;function Ia(e){return Fa||(Fa=Te(()=>{let t=sessionStorage.getItem(__prefix("__source"));if(t)return F(JSON.parse(t));{let r=hi(e.href);return r.subscribe(n=>{try{sessionStorage.setItem(__prefix("__source"),JSON.stringify(n))}catch(o){}}),r}}).pipe(rt(()=>ee),L(t=>Object.keys(t).length>0),p(t=>({facts:t})),re(1)))}function bi(e){let t=new _;return t.subscribe(({facts:r})=>{Fo(e,Do(r)),Io(e,"done")}),Ia(e).pipe(k(t),V(()=>t.complete()),p(r=>$({ref:e},r)))}function Ra(e,{viewport$:t,header$:r}){return He(document.body).pipe(E(()=>Rt(e,{header$:r,viewport$:t})),p(({offset:{y:n}})=>({hidden:n>=10})),N("hidden"))}function vi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Ro(e,"hidden"):fr(e)},complete(){fr(e)}}),Ra(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function Pa(e,{viewport$:t,header$:r}){let n=new Map;for(let a of e){let s=decodeURIComponent(a.hash.substring(1)),c=ae(`[id="${s}"]`);typeof c!="undefined"&&n.set(a,c)}let o=r.pipe(p(a=>24+a.height));return He(document.body).pipe(N("height"),p(()=>{let a=[];return[...n].reduce((s,[c,u])=>{for(;a.length&&n.get(a[a.length-1]).tagName>=u.tagName;)a.pop();let l=u.offsetTop;for(;!l&&u.parentElement;)u=u.parentElement,l=u.offsetTop;return s.set([...a=[...a,c]].reverse(),l)},new Map)}),p(a=>new Map([...a].sort(([,s],[,c])=>s-c))),E(a=>B([o,t]).pipe(Hn(([s,c],[u,{offset:{y:l}}])=>{for(;c.length;){let[,m]=c[0];if(m-u<l)s=[...s,c.shift()];else break}for(;s.length;){let[,m]=s[s.length-1];if(m-u>=l)c=[s.pop(),...c];else break}return[s,c]},[[],[...a]]),Q((s,c)=>s[0]===c[0]&&s[1]===c[1])))).pipe(p(([a,s])=>({prev:a.map(([c])=>c),next:s.map(([c])=>c)})),D({prev:[],next:[]}),ve(2,1),p(([a,s])=>a.prev.length<s.prev.length?{prev:s.prev.slice(Math.max(0,a.prev.length-1),s.prev.length),next:[]}:{prev:s.prev.slice(-1),next:s.next.slice(0,s.next.length-a.next.length)}))}function gi(e,t){let r=new _;r.pipe(Y(J)).subscribe(({prev:o,next:i})=>{for(let[a]of i)mo(a),fo(a);for(let[a,[s]]of o.entries())po(s,a===o.length-1),lo(s,"blur")});let n=q("[href^=\\#]",e);return Pa(n,t).pipe(k(r),V(()=>r.complete()),p(o=>$({ref:e},o)))}function $a(e,{viewport$:t,main$:r}){let n=t.pipe(p(({offset:{y:i}})=>i),ve(2,1),p(([i,a])=>i>a),Q()),o=r.pipe(N("active"));return B([o,n]).pipe(p(([{active:i},a])=>({hidden:!(i&&a)})),Q((i,a)=>i.hidden===a.hidden))}function yi(e,t){let r=new _;return r.pipe(Y(J)).subscribe({next({hidden:n}){n?Po(e,"hidden"):pr(e)},complete(){pr(e)}}),$a(e,t).pipe(k(r),V(()=>r.complete()),p(n=>$({ref:e},n)))}function xi({document$:e,tablet$:t}){e.pipe(E(()=>F(...q("[data-md-state=indeterminate]"))),k(r=>{r.indeterminate=!0,r.checked=!1}),te(r=>O(r,"change").pipe(Fn(()=>r.hasAttribute("data-md-state")),ne(r))),ge(t)).subscribe(([r,n])=>{r.removeAttribute("data-md-state"),n&&(r.checked=!1)})}function Va(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(E(()=>F(...q("[data-md-scrollfix]"))),k(t=>t.removeAttribute("data-md-scrollfix")),L(Va),te(t=>O(t,"touchstart").pipe(ne(t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function wi({viewport$:e,tablet$:t}){B([Ft("search"),t]).pipe(p(([r,n])=>r&&!n),E(r=>F(r).pipe(Me(r?400:100),Y(J))),ge(e)).subscribe(([r,{offset:{y:n}}])=>{r?co(document.body,n):uo(document.body)})}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var Je=$n(),br=Jn(),vr=Gn(),gr=Qn(),le=oo(),Dt=qe("(min-width: 960px)"),Ei=qe("(min-width: 1220px)"),Oi=Xn(),Ti=se(),Da=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||ye(`${Ti.base}/search/search_index.json`):ee,yr=new _;ti({alert$:yr});Pt("navigation.instant")&&ri({document$:Je,location$:br,viewport$:le});var _i;((_i=Ti.version)==null?void 0:_i.provider)==="mike"&&ui();R(br,vr).pipe(Me(125)).subscribe(()=>{Fe("drawer",!1),Fe("search",!1)});gr.pipe(L(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ae("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=ae("[href][rel=next]");typeof r!="undefined"&&r.click();break}});xi({document$:Je,tablet$:Dt});Si({document$:Je});wi({viewport$:le,tablet$:Dt});var Re=Bo(Ce("header"),{viewport$:le}),Ut=Je.pipe(p(()=>Ce("main")),E(e=>Zo(e,{viewport$:le,header$:Re})),re(1)),Ua=R(...me("dialog").map(e=>Yo(e,{alert$:yr})),...me("header").map(e=>Go(e,{viewport$:le,header$:Re,main$:Ut})),...me("palette").map(e=>ei(e)),...me("search").map(e=>pi(e,{index$:Da,keyboard$:gr})),...me("source").map(e=>bi(e))),Na=Te(()=>R(...me("content").map(e=>Jo(e,{target$:vr,viewport$:le,print$:Oi})),...me("header-title").map(e=>Xo(e,{viewport$:le,header$:Re})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?sr(Ei,()=>hr(e,{viewport$:le,header$:Re,main$:Ut})):sr(Dt,()=>hr(e,{viewport$:le,header$:Re,main$:Ut}))),...me("tabs").map(e=>vi(e,{viewport$:le,header$:Re})),...me("toc").map(e=>gi(e,{viewport$:le,header$:Re})),...me("top").map(e=>yi(e,{viewport$:le,main$:Ut})))),Mi=Je.pipe(E(()=>Na),Ct(Ua),re(1));Mi.subscribe();window.document$=Je;window.location$=br;window.target$=vr;window.keyboard$=gr;window.viewport$=le;window.tablet$=Dt;window.screen$=Ei;window.print$=Oi;window.alert$=yr;window.component$=Mi;})();
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+//# sourceMappingURL=bundle.d892486b.min.js.map
+
diff --git a/latest/assets/javascripts/bundle.d892486b.min.js.map b/latest/assets/javascripts/bundle.d892486b.min.js.map
new file mode 100644 (file)
index 0000000..e57c79d
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "version": 3,
+  "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/tslib/tslib.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/assets/javascripts/bundle.ts", "node_modules/tslib/modules/index.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/caughtSchedule.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/fromArray.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/switchMapTo.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/size/index.ts", "src/assets/javascripts/browser/element/offset/index.ts", "src/assets/javascripts/browser/element/selection/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/index.ts", "src/assets/javascripts/actions/_/index.ts", "src/assets/javascripts/actions/anchor/index.ts", "src/assets/javascripts/actions/dialog/index.ts", "src/assets/javascripts/actions/header/_/index.ts", "src/assets/javascripts/actions/header/title/index.ts", "src/assets/javascripts/actions/search/query/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/actions/search/result/index.ts", "src/assets/javascripts/actions/sidebar/index.ts", "src/assets/javascripts/actions/source/index.ts", "src/assets/javascripts/actions/tabs/index.ts", "src/assets/javascripts/actions/top/index.ts", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/components/palette/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/components/top/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts"],
+  "sourcesContent": ["(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (factory());\n}(this, (function () { 'use strict';\n\n  /**\n   * Applies the :focus-visible polyfill at the given scope.\n   * A scope in this case is either the top-level Document or a Shadow Root.\n   *\n   * @param {(Document|ShadowRoot)} scope\n   * @see https://github.com/WICG/focus-visible\n   */\n  function applyFocusVisiblePolyfill(scope) {\n    var hadKeyboardEvent = true;\n    var hadFocusVisibleRecently = false;\n    var hadFocusVisibleRecentlyTimeout = null;\n\n    var inputTypesAllowlist = {\n      text: true,\n      search: true,\n      url: true,\n      tel: true,\n      email: true,\n      password: true,\n      number: true,\n      date: true,\n      month: true,\n      week: true,\n      time: true,\n      datetime: true,\n      'datetime-local': true\n    };\n\n    /**\n     * Helper function for legacy browsers and iframes which sometimes focus\n     * elements like document, body, and non-interactive SVG.\n     * @param {Element} el\n     */\n    function isValidFocusTarget(el) {\n      if (\n        el &&\n        el !== document &&\n        el.nodeName !== 'HTML' &&\n        el.nodeName !== 'BODY' &&\n        'classList' in el &&\n        'contains' in el.classList\n      ) {\n        return true;\n      }\n      return false;\n    }\n\n    /**\n     * Computes whether the given element should automatically trigger the\n     * `focus-visible` class being added, i.e. whether it should always match\n     * `:focus-visible` when focused.\n     * @param {Element} el\n     * @return {boolean}\n     */\n    function focusTriggersKeyboardModality(el) {\n      var type = el.type;\n      var tagName = el.tagName;\n\n      if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n        return true;\n      }\n\n      if (tagName === 'TEXTAREA' && !el.readOnly) {\n        return true;\n      }\n\n      if (el.isContentEditable) {\n        return true;\n      }\n\n      return false;\n    }\n\n    /**\n     * Add the `focus-visible` class to the given element if it was not added by\n     * the author.\n     * @param {Element} el\n     */\n    function addFocusVisibleClass(el) {\n      if (el.classList.contains('focus-visible')) {\n        return;\n      }\n      el.classList.add('focus-visible');\n      el.setAttribute('data-focus-visible-added', '');\n    }\n\n    /**\n     * Remove the `focus-visible` class from the given element if it was not\n     * originally added by the author.\n     * @param {Element} el\n     */\n    function removeFocusVisibleClass(el) {\n      if (!el.hasAttribute('data-focus-visible-added')) {\n        return;\n      }\n      el.classList.remove('focus-visible');\n      el.removeAttribute('data-focus-visible-added');\n    }\n\n    /**\n     * If the most recent user interaction was via the keyboard;\n     * and the key press did not include a meta, alt/option, or control key;\n     * then the modality is keyboard. Otherwise, the modality is not keyboard.\n     * Apply `focus-visible` to any current active element and keep track\n     * of our keyboard modality state with `hadKeyboardEvent`.\n     * @param {KeyboardEvent} e\n     */\n    function onKeyDown(e) {\n      if (e.metaKey || e.altKey || e.ctrlKey) {\n        return;\n      }\n\n      if (isValidFocusTarget(scope.activeElement)) {\n        addFocusVisibleClass(scope.activeElement);\n      }\n\n      hadKeyboardEvent = true;\n    }\n\n    /**\n     * If at any point a user clicks with a pointing device, ensure that we change\n     * the modality away from keyboard.\n     * This avoids the situation where a user presses a key on an already focused\n     * element, and then clicks on a different element, focusing it with a\n     * pointing device, while we still think we're in keyboard modality.\n     * @param {Event} e\n     */\n    function onPointerDown(e) {\n      hadKeyboardEvent = false;\n    }\n\n    /**\n     * On `focus`, add the `focus-visible` class to the target if:\n     * - the target received focus as a result of keyboard navigation, or\n     * - the event target is an element that will likely require interaction\n     *   via the keyboard (e.g. a text box)\n     * @param {Event} e\n     */\n    function onFocus(e) {\n      // Prevent IE from focusing the document or HTML element.\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n        addFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * On `blur`, remove the `focus-visible` class from the target.\n     * @param {Event} e\n     */\n    function onBlur(e) {\n      if (!isValidFocusTarget(e.target)) {\n        return;\n      }\n\n      if (\n        e.target.classList.contains('focus-visible') ||\n        e.target.hasAttribute('data-focus-visible-added')\n      ) {\n        // To detect a tab/window switch, we look for a blur event followed\n        // rapidly by a visibility change.\n        // If we don't see a visibility change within 100ms, it's probably a\n        // regular focus change.\n        hadFocusVisibleRecently = true;\n        window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n        hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n          hadFocusVisibleRecently = false;\n        }, 100);\n        removeFocusVisibleClass(e.target);\n      }\n    }\n\n    /**\n     * If the user changes tabs, keep track of whether or not the previously\n     * focused element had .focus-visible.\n     * @param {Event} e\n     */\n    function onVisibilityChange(e) {\n      if (document.visibilityState === 'hidden') {\n        // If the tab becomes active again, the browser will handle calling focus\n        // on the element (Safari actually calls it twice).\n        // If this tab change caused a blur on an element with focus-visible,\n        // re-apply the class when the user switches back to the tab.\n        if (hadFocusVisibleRecently) {\n          hadKeyboardEvent = true;\n        }\n        addInitialPointerMoveListeners();\n      }\n    }\n\n    /**\n     * Add a group of listeners to detect usage of any pointing devices.\n     * These listeners will be added when the polyfill first loads, and anytime\n     * the window is blurred, so that they are active when the window regains\n     * focus.\n     */\n    function addInitialPointerMoveListeners() {\n      document.addEventListener('mousemove', onInitialPointerMove);\n      document.addEventListener('mousedown', onInitialPointerMove);\n      document.addEventListener('mouseup', onInitialPointerMove);\n      document.addEventListener('pointermove', onInitialPointerMove);\n      document.addEventListener('pointerdown', onInitialPointerMove);\n      document.addEventListener('pointerup', onInitialPointerMove);\n      document.addEventListener('touchmove', onInitialPointerMove);\n      document.addEventListener('touchstart', onInitialPointerMove);\n      document.addEventListener('touchend', onInitialPointerMove);\n    }\n\n    function removeInitialPointerMoveListeners() {\n      document.removeEventListener('mousemove', onInitialPointerMove);\n      document.removeEventListener('mousedown', onInitialPointerMove);\n      document.removeEventListener('mouseup', onInitialPointerMove);\n      document.removeEventListener('pointermove', onInitialPointerMove);\n      document.removeEventListener('pointerdown', onInitialPointerMove);\n      document.removeEventListener('pointerup', onInitialPointerMove);\n      document.removeEventListener('touchmove', onInitialPointerMove);\n      document.removeEventListener('touchstart', onInitialPointerMove);\n      document.removeEventListener('touchend', onInitialPointerMove);\n    }\n\n    /**\n     * When the polfyill first loads, assume the user is in keyboard modality.\n     * If any event is received from a pointing device (e.g. mouse, pointer,\n     * touch), turn off keyboard modality.\n     * This accounts for situations where focus enters the page from the URL bar.\n     * @param {Event} e\n     */\n    function onInitialPointerMove(e) {\n      // Work around a Safari quirk that fires a mousemove on <html> whenever the\n      // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n      if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n        return;\n      }\n\n      hadKeyboardEvent = false;\n      removeInitialPointerMoveListeners();\n    }\n\n    // For some kinds of state, we are interested in changes at the global scope\n    // only. For example, global pointer input, global key presses and global\n    // visibility change should affect the state at every scope:\n    document.addEventListener('keydown', onKeyDown, true);\n    document.addEventListener('mousedown', onPointerDown, true);\n    document.addEventListener('pointerdown', onPointerDown, true);\n    document.addEventListener('touchstart', onPointerDown, true);\n    document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n    addInitialPointerMoveListeners();\n\n    // For focus and blur, we specifically care about state changes in the local\n    // scope. This is because focus / blur events that originate from within a\n    // shadow root are not re-dispatched from the host element if it was already\n    // the active element in its own scope:\n    scope.addEventListener('focus', onFocus, true);\n    scope.addEventListener('blur', onBlur, true);\n\n    // We detect that a node is a ShadowRoot by ensuring that it is a\n    // DocumentFragment and also has a host property. This check covers native\n    // implementation and polyfill implementation transparently. If we only cared\n    // about the native implementation, we could just check if the scope was\n    // an instance of a ShadowRoot.\n    if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n      // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n      // have a root element to add a class to. So, we add this attribute to the\n      // host element instead:\n      scope.host.setAttribute('data-js-focus-visible', '');\n    } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n      document.documentElement.classList.add('js-focus-visible');\n      document.documentElement.setAttribute('data-js-focus-visible', '');\n    }\n  }\n\n  // It is important to wrap all references to global window and document in\n  // these checks to support server-side rendering use cases\n  // @see https://github.com/WICG/focus-visible/issues/199\n  if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n    // Make the polyfill helper globally available. This can be used as a signal\n    // to interested libraries that wish to coordinate with the polyfill for e.g.,\n    // applying the polyfill to a shadow root:\n    window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n    // Notify interested libraries of the polyfill's presence, in case the\n    // polyfill was loaded lazily:\n    var event;\n\n    try {\n      event = new CustomEvent('focus-visible-polyfill-ready');\n    } catch (error) {\n      // IE11 does not support using CustomEvent as a constructor directly:\n      event = document.createEvent('CustomEvent');\n      event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n    }\n\n    window.dispatchEvent(event);\n  }\n\n  if (typeof document !== 'undefined') {\n    // Apply the polyfill to the global document, so that no JavaScript\n    // coordination is required to use the polyfill in the top-level document:\n    applyFocusVisiblePolyfill(document);\n  }\n\n})));\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global global, define, System, Reflect, Promise */\r\nvar __extends;\r\nvar __assign;\r\nvar __rest;\r\nvar __decorate;\r\nvar __param;\r\nvar __metadata;\r\nvar __awaiter;\r\nvar __generator;\r\nvar __exportStar;\r\nvar __values;\r\nvar __read;\r\nvar __spread;\r\nvar __spreadArrays;\r\nvar __spreadArray;\r\nvar __await;\r\nvar __asyncGenerator;\r\nvar __asyncDelegator;\r\nvar __asyncValues;\r\nvar __makeTemplateObject;\r\nvar __importStar;\r\nvar __importDefault;\r\nvar __classPrivateFieldGet;\r\nvar __classPrivateFieldSet;\r\nvar __createBinding;\r\n(function (factory) {\r\n    var root = typeof global === \"object\" ? global : typeof self === \"object\" ? self : typeof this === \"object\" ? this : {};\r\n    if (typeof define === \"function\" && define.amd) {\r\n        define(\"tslib\", [\"exports\"], function (exports) { factory(createExporter(root, createExporter(exports))); });\r\n    }\r\n    else if (typeof module === \"object\" && typeof module.exports === \"object\") {\r\n        factory(createExporter(root, createExporter(module.exports)));\r\n    }\r\n    else {\r\n        factory(createExporter(root));\r\n    }\r\n    function createExporter(exports, previous) {\r\n        if (exports !== root) {\r\n            if (typeof Object.create === \"function\") {\r\n                Object.defineProperty(exports, \"__esModule\", { value: true });\r\n            }\r\n            else {\r\n                exports.__esModule = true;\r\n            }\r\n        }\r\n        return function (id, v) { return exports[id] = previous ? previous(id, v) : v; };\r\n    }\r\n})\r\n(function (exporter) {\r\n    var extendStatics = Object.setPrototypeOf ||\r\n        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n        function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n\r\n    __extends = function (d, b) {\r\n        if (typeof b !== \"function\" && b !== null)\r\n            throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n        extendStatics(d, b);\r\n        function __() { this.constructor = d; }\r\n        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n    };\r\n\r\n    __assign = Object.assign || function (t) {\r\n        for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n            s = arguments[i];\r\n            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n        }\r\n        return t;\r\n    };\r\n\r\n    __rest = function (s, e) {\r\n        var t = {};\r\n        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n            t[p] = s[p];\r\n        if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n            for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n                if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n                    t[p[i]] = s[p[i]];\r\n            }\r\n        return t;\r\n    };\r\n\r\n    __decorate = function (decorators, target, key, desc) {\r\n        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n        if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n        return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n    };\r\n\r\n    __param = function (paramIndex, decorator) {\r\n        return function (target, key) { decorator(target, key, paramIndex); }\r\n    };\r\n\r\n    __metadata = function (metadataKey, metadataValue) {\r\n        if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n    };\r\n\r\n    __awaiter = function (thisArg, _arguments, P, generator) {\r\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n        return new (P || (P = Promise))(function (resolve, reject) {\r\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n        });\r\n    };\r\n\r\n    __generator = function (thisArg, body) {\r\n        var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n        return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n        function verb(n) { return function (v) { return step([n, v]); }; }\r\n        function step(op) {\r\n            if (f) throw new TypeError(\"Generator is already executing.\");\r\n            while (_) try {\r\n                if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n                if (y = 0, t) op = [op[0] & 2, t.value];\r\n                switch (op[0]) {\r\n                    case 0: case 1: t = op; break;\r\n                    case 4: _.label++; return { value: op[1], done: false };\r\n                    case 5: _.label++; y = op[1]; op = [0]; continue;\r\n                    case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n                    default:\r\n                        if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n                        if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n                        if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n                        if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n                        if (t[2]) _.ops.pop();\r\n                        _.trys.pop(); continue;\r\n                }\r\n                op = body.call(thisArg, _);\r\n            } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n            if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n        }\r\n    };\r\n\r\n    __exportStar = function(m, o) {\r\n        for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n    };\r\n\r\n    __createBinding = Object.create ? (function(o, m, k, k2) {\r\n        if (k2 === undefined) k2 = k;\r\n        Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n    }) : (function(o, m, k, k2) {\r\n        if (k2 === undefined) k2 = k;\r\n        o[k2] = m[k];\r\n    });\r\n\r\n    __values = function (o) {\r\n        var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n        if (m) return m.call(o);\r\n        if (o && typeof o.length === \"number\") return {\r\n            next: function () {\r\n                if (o && i >= o.length) o = void 0;\r\n                return { value: o && o[i++], done: !o };\r\n            }\r\n        };\r\n        throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n    };\r\n\r\n    __read = function (o, n) {\r\n        var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n        if (!m) return o;\r\n        var i = m.call(o), r, ar = [], e;\r\n        try {\r\n            while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n        }\r\n        catch (error) { e = { error: error }; }\r\n        finally {\r\n            try {\r\n                if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n            }\r\n            finally { if (e) throw e.error; }\r\n        }\r\n        return ar;\r\n    };\r\n\r\n    /** @deprecated */\r\n    __spread = function () {\r\n        for (var ar = [], i = 0; i < arguments.length; i++)\r\n            ar = ar.concat(__read(arguments[i]));\r\n        return ar;\r\n    };\r\n\r\n    /** @deprecated */\r\n    __spreadArrays = function () {\r\n        for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n        for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n            for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n                r[k] = a[j];\r\n        return r;\r\n    };\r\n\r\n    __spreadArray = function (to, from) {\r\n        for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)\r\n            to[j] = from[i];\r\n        return to;\r\n    };\r\n\r\n    __await = function (v) {\r\n        return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n    };\r\n\r\n    __asyncGenerator = function (thisArg, _arguments, generator) {\r\n        if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n        var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n        return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n        function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n        function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n        function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);  }\r\n        function fulfill(value) { resume(\"next\", value); }\r\n        function reject(value) { resume(\"throw\", value); }\r\n        function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n    };\r\n\r\n    __asyncDelegator = function (o) {\r\n        var i, p;\r\n        return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n        function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n    };\r\n\r\n    __asyncValues = function (o) {\r\n        if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n        var m = o[Symbol.asyncIterator], i;\r\n        return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n        function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n        function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n    };\r\n\r\n    __makeTemplateObject = function (cooked, raw) {\r\n        if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n        return cooked;\r\n    };\r\n\r\n    var __setModuleDefault = Object.create ? (function(o, v) {\r\n        Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n    }) : function(o, v) {\r\n        o[\"default\"] = v;\r\n    };\r\n\r\n    __importStar = function (mod) {\r\n        if (mod && mod.__esModule) return mod;\r\n        var result = {};\r\n        if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n        __setModuleDefault(result, mod);\r\n        return result;\r\n    };\r\n\r\n    __importDefault = function (mod) {\r\n        return (mod && mod.__esModule) ? mod : { \"default\": mod };\r\n    };\r\n\r\n    __classPrivateFieldGet = function (receiver, privateMap) {\r\n        if (!privateMap.has(receiver)) {\r\n            throw new TypeError(\"attempted to get private field on non-instance\");\r\n        }\r\n        return privateMap.get(receiver);\r\n    };\r\n\r\n    __classPrivateFieldSet = function (receiver, privateMap, value) {\r\n        if (!privateMap.has(receiver)) {\r\n            throw new TypeError(\"attempted to set private field on non-instance\");\r\n        }\r\n        privateMap.set(receiver, value);\r\n        return value;\r\n    };\r\n\r\n    exporter(\"__extends\", __extends);\r\n    exporter(\"__assign\", __assign);\r\n    exporter(\"__rest\", __rest);\r\n    exporter(\"__decorate\", __decorate);\r\n    exporter(\"__param\", __param);\r\n    exporter(\"__metadata\", __metadata);\r\n    exporter(\"__awaiter\", __awaiter);\r\n    exporter(\"__generator\", __generator);\r\n    exporter(\"__exportStar\", __exportStar);\r\n    exporter(\"__createBinding\", __createBinding);\r\n    exporter(\"__values\", __values);\r\n    exporter(\"__read\", __read);\r\n    exporter(\"__spread\", __spread);\r\n    exporter(\"__spreadArrays\", __spreadArrays);\r\n    exporter(\"__spreadArray\", __spreadArray);\r\n    exporter(\"__await\", __await);\r\n    exporter(\"__asyncGenerator\", __asyncGenerator);\r\n    exporter(\"__asyncDelegator\", __asyncDelegator);\r\n    exporter(\"__asyncValues\", __asyncValues);\r\n    exporter(\"__makeTemplateObject\", __makeTemplateObject);\r\n    exporter(\"__importStar\", __importStar);\r\n    exporter(\"__importDefault\", __importDefault);\r\n    exporter(\"__classPrivateFieldGet\", __classPrivateFieldGet);\r\n    exporter(\"__classPrivateFieldSet\", __classPrivateFieldSet);\r\n});\r\n", "/*!\n * clipboard.js v2.0.8\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 134:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n  \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/clipboard-action.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n/**\n * Inner class which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n */\n\nvar ClipboardAction = /*#__PURE__*/function () {\n  /**\n   * @param {Object} options\n   */\n  function ClipboardAction(options) {\n    _classCallCheck(this, ClipboardAction);\n\n    this.resolveOptions(options);\n    this.initSelection();\n  }\n  /**\n   * Defines base properties passed from constructor.\n   * @param {Object} options\n   */\n\n\n  _createClass(ClipboardAction, [{\n    key: \"resolveOptions\",\n    value: function resolveOptions() {\n      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      this.action = options.action;\n      this.container = options.container;\n      this.emitter = options.emitter;\n      this.target = options.target;\n      this.text = options.text;\n      this.trigger = options.trigger;\n      this.selectedText = '';\n    }\n    /**\n     * Decides which selection strategy is going to be applied based\n     * on the existence of `text` and `target` properties.\n     */\n\n  }, {\n    key: \"initSelection\",\n    value: function initSelection() {\n      if (this.text) {\n        this.selectFake();\n      } else if (this.target) {\n        this.selectTarget();\n      }\n    }\n    /**\n     * Creates a fake textarea element, sets its value from `text` property,\n     */\n\n  }, {\n    key: \"createFakeElement\",\n    value: function createFakeElement() {\n      var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n      this.fakeElem = document.createElement('textarea'); // Prevent zooming on iOS\n\n      this.fakeElem.style.fontSize = '12pt'; // Reset box model\n\n      this.fakeElem.style.border = '0';\n      this.fakeElem.style.padding = '0';\n      this.fakeElem.style.margin = '0'; // Move element out of screen horizontally\n\n      this.fakeElem.style.position = 'absolute';\n      this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n      var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n      this.fakeElem.style.top = \"\".concat(yPosition, \"px\");\n      this.fakeElem.setAttribute('readonly', '');\n      this.fakeElem.value = this.text;\n      return this.fakeElem;\n    }\n    /**\n     * Get's the value of fakeElem,\n     * and makes a selection on it.\n     */\n\n  }, {\n    key: \"selectFake\",\n    value: function selectFake() {\n      var _this = this;\n\n      var fakeElem = this.createFakeElement();\n\n      this.fakeHandlerCallback = function () {\n        return _this.removeFake();\n      };\n\n      this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;\n      this.container.appendChild(fakeElem);\n      this.selectedText = select_default()(fakeElem);\n      this.copyText();\n      this.removeFake();\n    }\n    /**\n     * Only removes the fake element after another click event, that way\n     * a user can hit `Ctrl+C` to copy because selection still exists.\n     */\n\n  }, {\n    key: \"removeFake\",\n    value: function removeFake() {\n      if (this.fakeHandler) {\n        this.container.removeEventListener('click', this.fakeHandlerCallback);\n        this.fakeHandler = null;\n        this.fakeHandlerCallback = null;\n      }\n\n      if (this.fakeElem) {\n        this.container.removeChild(this.fakeElem);\n        this.fakeElem = null;\n      }\n    }\n    /**\n     * Selects the content from element passed on `target` property.\n     */\n\n  }, {\n    key: \"selectTarget\",\n    value: function selectTarget() {\n      this.selectedText = select_default()(this.target);\n      this.copyText();\n    }\n    /**\n     * Executes the copy operation based on the current selection.\n     */\n\n  }, {\n    key: \"copyText\",\n    value: function copyText() {\n      var succeeded;\n\n      try {\n        succeeded = document.execCommand(this.action);\n      } catch (err) {\n        succeeded = false;\n      }\n\n      this.handleResult(succeeded);\n    }\n    /**\n     * Fires an event based on the copy operation result.\n     * @param {Boolean} succeeded\n     */\n\n  }, {\n    key: \"handleResult\",\n    value: function handleResult(succeeded) {\n      this.emitter.emit(succeeded ? 'success' : 'error', {\n        action: this.action,\n        text: this.selectedText,\n        trigger: this.trigger,\n        clearSelection: this.clearSelection.bind(this)\n      });\n    }\n    /**\n     * Moves focus away from `target` and back to the trigger, removes current selection.\n     */\n\n  }, {\n    key: \"clearSelection\",\n    value: function clearSelection() {\n      if (this.trigger) {\n        this.trigger.focus();\n      }\n\n      document.activeElement.blur();\n      window.getSelection().removeAllRanges();\n    }\n    /**\n     * Sets the `action` to be performed which can be either 'copy' or 'cut'.\n     * @param {String} action\n     */\n\n  }, {\n    key: \"destroy\",\n\n    /**\n     * Destroy lifecycle.\n     */\n    value: function destroy() {\n      this.removeFake();\n    }\n  }, {\n    key: \"action\",\n    set: function set() {\n      var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';\n      this._action = action;\n\n      if (this._action !== 'copy' && this._action !== 'cut') {\n        throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n      }\n    }\n    /**\n     * Gets the `action` property.\n     * @return {String}\n     */\n    ,\n    get: function get() {\n      return this._action;\n    }\n    /**\n     * Sets the `target` property using an element\n     * that will be have its content copied.\n     * @param {Element} target\n     */\n\n  }, {\n    key: \"target\",\n    set: function set(target) {\n      if (target !== undefined) {\n        if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n          if (this.action === 'copy' && target.hasAttribute('disabled')) {\n            throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n          }\n\n          if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n            throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n          }\n\n          this._target = target;\n        } else {\n          throw new Error('Invalid \"target\" value, use a valid Element');\n        }\n      }\n    }\n    /**\n     * Gets the `target` property.\n     * @return {String|HTMLElement}\n     */\n    ,\n    get: function get() {\n      return this._target;\n    }\n  }]);\n\n  return ClipboardAction;\n}();\n\n/* harmony default export */ var clipboard_action = (ClipboardAction);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction clipboard_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) clipboard_defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n  var attribute = \"data-clipboard-\".concat(suffix);\n\n  if (!element.hasAttribute(attribute)) {\n    return;\n  }\n\n  return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n  _inherits(Clipboard, _Emitter);\n\n  var _super = _createSuper(Clipboard);\n\n  /**\n   * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n   * @param {Object} options\n   */\n  function Clipboard(trigger, options) {\n    var _this;\n\n    clipboard_classCallCheck(this, Clipboard);\n\n    _this = _super.call(this);\n\n    _this.resolveOptions(options);\n\n    _this.listenClick(trigger);\n\n    return _this;\n  }\n  /**\n   * Defines if attributes would be resolved using internal setter functions\n   * or custom functions that were passed in the constructor.\n   * @param {Object} options\n   */\n\n\n  clipboard_createClass(Clipboard, [{\n    key: \"resolveOptions\",\n    value: function resolveOptions() {\n      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n      this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n      this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n      this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n      this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n    }\n    /**\n     * Adds a click event listener to the passed trigger.\n     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n     */\n\n  }, {\n    key: \"listenClick\",\n    value: function listenClick(trigger) {\n      var _this2 = this;\n\n      this.listener = listen_default()(trigger, 'click', function (e) {\n        return _this2.onClick(e);\n      });\n    }\n    /**\n     * Defines a new `ClipboardAction` on each click event.\n     * @param {Event} e\n     */\n\n  }, {\n    key: \"onClick\",\n    value: function onClick(e) {\n      var trigger = e.delegateTarget || e.currentTarget;\n\n      if (this.clipboardAction) {\n        this.clipboardAction = null;\n      }\n\n      this.clipboardAction = new clipboard_action({\n        action: this.action(trigger),\n        target: this.target(trigger),\n        text: this.text(trigger),\n        container: this.container,\n        trigger: trigger,\n        emitter: this\n      });\n    }\n    /**\n     * Default `action` lookup function.\n     * @param {Element} trigger\n     */\n\n  }, {\n    key: \"defaultAction\",\n    value: function defaultAction(trigger) {\n      return getAttributeValue('action', trigger);\n    }\n    /**\n     * Default `target` lookup function.\n     * @param {Element} trigger\n     */\n\n  }, {\n    key: \"defaultTarget\",\n    value: function defaultTarget(trigger) {\n      var selector = getAttributeValue('target', trigger);\n\n      if (selector) {\n        return document.querySelector(selector);\n      }\n    }\n    /**\n     * Returns the support of the given action, or all actions if no action is\n     * given.\n     * @param {String} [action]\n     */\n\n  }, {\n    key: \"defaultText\",\n\n    /**\n     * Default `text` lookup function.\n     * @param {Element} trigger\n     */\n    value: function defaultText(trigger) {\n      return getAttributeValue('text', trigger);\n    }\n    /**\n     * Destroy lifecycle.\n     */\n\n  }, {\n    key: \"destroy\",\n    value: function destroy() {\n      this.listener.destroy();\n\n      if (this.clipboardAction) {\n        this.clipboardAction.destroy();\n        this.clipboardAction = null;\n      }\n    }\n  }], [{\n    key: \"isSupported\",\n    value: function isSupported() {\n      var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n      var actions = typeof action === 'string' ? [action] : action;\n      var support = !!document.queryCommandSupported;\n      actions.forEach(function (action) {\n        support = support && !!document.queryCommandSupported(action);\n      });\n      return support;\n    }\n  }]);\n\n  return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n    var proto = Element.prototype;\n\n    proto.matches = proto.matchesSelector ||\n                    proto.mozMatchesSelector ||\n                    proto.msMatchesSelector ||\n                    proto.oMatchesSelector ||\n                    proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n    while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n        if (typeof element.matches === 'function' &&\n            element.matches(selector)) {\n          return element;\n        }\n        element = element.parentNode;\n    }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n    var listenerFn = listener.apply(this, arguments);\n\n    element.addEventListener(type, listenerFn, useCapture);\n\n    return {\n        destroy: function() {\n            element.removeEventListener(type, listenerFn, useCapture);\n        }\n    }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n    // Handle the regular Element usage\n    if (typeof elements.addEventListener === 'function') {\n        return _delegate.apply(null, arguments);\n    }\n\n    // Handle Element-less usage, it defaults to global delegation\n    if (typeof type === 'function') {\n        // Use `document` as the first parameter, then apply arguments\n        // This is a short way to .unshift `arguments` without running into deoptimizations\n        return _delegate.bind(null, document).apply(null, arguments);\n    }\n\n    // Handle Selector-based usage\n    if (typeof elements === 'string') {\n        elements = document.querySelectorAll(elements);\n    }\n\n    // Handle Array-like based usage\n    return Array.prototype.map.call(elements, function (element) {\n        return _delegate(element, selector, type, callback, useCapture);\n    });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n    return function(e) {\n        e.delegateTarget = closest(e.target, selector);\n\n        if (e.delegateTarget) {\n            callback.call(element, e);\n        }\n    }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n    return value !== undefined\n        && value instanceof HTMLElement\n        && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return value !== undefined\n        && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n        && ('length' in value)\n        && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n    return typeof value === 'string'\n        || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n    var type = Object.prototype.toString.call(value);\n\n    return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n    if (!target && !type && !callback) {\n        throw new Error('Missing required arguments');\n    }\n\n    if (!is.string(type)) {\n        throw new TypeError('Second argument must be a String');\n    }\n\n    if (!is.fn(callback)) {\n        throw new TypeError('Third argument must be a Function');\n    }\n\n    if (is.node(target)) {\n        return listenNode(target, type, callback);\n    }\n    else if (is.nodeList(target)) {\n        return listenNodeList(target, type, callback);\n    }\n    else if (is.string(target)) {\n        return listenSelector(target, type, callback);\n    }\n    else {\n        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n    }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n    node.addEventListener(type, callback);\n\n    return {\n        destroy: function() {\n            node.removeEventListener(type, callback);\n        }\n    }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n    Array.prototype.forEach.call(nodeList, function(node) {\n        node.addEventListener(type, callback);\n    });\n\n    return {\n        destroy: function() {\n            Array.prototype.forEach.call(nodeList, function(node) {\n                node.removeEventListener(type, callback);\n            });\n        }\n    }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n    return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n    var selectedText;\n\n    if (element.nodeName === 'SELECT') {\n        element.focus();\n\n        selectedText = element.value;\n    }\n    else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n        var isReadOnly = element.hasAttribute('readonly');\n\n        if (!isReadOnly) {\n            element.setAttribute('readonly', '');\n        }\n\n        element.select();\n        element.setSelectionRange(0, element.value.length);\n\n        if (!isReadOnly) {\n            element.removeAttribute('readonly');\n        }\n\n        selectedText = element.value;\n    }\n    else {\n        if (element.hasAttribute('contenteditable')) {\n            element.focus();\n        }\n\n        var selection = window.getSelection();\n        var range = document.createRange();\n\n        range.selectNodeContents(element);\n        selection.removeAllRanges();\n        selection.addRange(range);\n\n        selectedText = selection.toString();\n    }\n\n    return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n  // Keep this empty so it's easier to inherit from\n  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n  on: function (name, callback, ctx) {\n    var e = this.e || (this.e = {});\n\n    (e[name] || (e[name] = [])).push({\n      fn: callback,\n      ctx: ctx\n    });\n\n    return this;\n  },\n\n  once: function (name, callback, ctx) {\n    var self = this;\n    function listener () {\n      self.off(name, listener);\n      callback.apply(ctx, arguments);\n    };\n\n    listener._ = callback\n    return this.on(name, listener, ctx);\n  },\n\n  emit: function (name) {\n    var data = [].slice.call(arguments, 1);\n    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n    var i = 0;\n    var len = evtArr.length;\n\n    for (i; i < len; i++) {\n      evtArr[i].fn.apply(evtArr[i].ctx, data);\n    }\n\n    return this;\n  },\n\n  off: function (name, callback) {\n    var e = this.e || (this.e = {});\n    var evts = e[name];\n    var liveEvents = [];\n\n    if (evts && callback) {\n      for (var i = 0, len = evts.length; i < len; i++) {\n        if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n          liveEvents.push(evts[i]);\n      }\n    }\n\n    // Remove event from queue to prevent memory leak\n    // Suggested by https://github.com/lazd\n    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n    (liveEvents.length)\n      ? e[name] = liveEvents\n      : delete e[name];\n\n    return this;\n  }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(134);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\nimport { NEVER, Subject, defer, merge } from \"rxjs\"\nimport {\n  delay,\n  filter,\n  map,\n  mergeWith,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n  at,\n  getElement,\n  requestJSON,\n  setToggle,\n  watchDocument,\n  watchKeyboard,\n  watchLocation,\n  watchLocationTarget,\n  watchMedia,\n  watchPrint,\n  watchViewport\n} from \"./browser\"\nimport {\n  getComponentElement,\n  getComponentElements,\n  mountBackToTop,\n  mountContent,\n  mountDialog,\n  mountHeader,\n  mountHeaderTitle,\n  mountPalette,\n  mountSearch,\n  mountSidebar,\n  mountSource,\n  mountTableOfContents,\n  mountTabs,\n  watchHeader,\n  watchMain\n} from \"./components\"\nimport {\n  SearchIndex,\n  setupClipboardJS,\n  setupInstantLoading,\n  setupVersionSelector\n} from \"./integrations\"\nimport {\n  patchIndeterminate,\n  patchScrollfix,\n  patchScrolllock\n} from \"./patches\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$   = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$   = watchMedia(\"(min-width: 960px)\")\nconst screen$   = watchMedia(\"(min-width: 1220px)\")\nconst print$    = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n  ? __search?.index || requestJSON<SearchIndex>(\n    `${config.base}/search/search_index.json`\n  )\n  : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject<string>()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n  setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n  setupVersionSelector()\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n  .pipe(\n    delay(125)\n  )\n    .subscribe(() => {\n      setToggle(\"drawer\", false)\n      setToggle(\"search\", false)\n    })\n\n/* Set up global keyboard handlers */\nkeyboard$\n  .pipe(\n    filter(({ mode }) => mode === \"global\")\n  )\n    .subscribe(key => {\n      switch (key.type) {\n\n        /* Go to previous page */\n        case \"p\":\n        case \",\":\n          const prev = getElement(\"[href][rel=prev]\")\n          if (typeof prev !== \"undefined\")\n            prev.click()\n          break\n\n        /* Go to next page */\n        case \"n\":\n        case \".\":\n          const next = getElement(\"[href][rel=next]\")\n          if (typeof next !== \"undefined\")\n            next.click()\n          break\n      }\n    })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n  .pipe(\n    map(() => getComponentElement(\"main\")),\n    switchMap(el => watchMain(el, { viewport$, header$ })),\n    shareReplay(1)\n  )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n  /* Dialog */\n  ...getComponentElements(\"dialog\")\n    .map(el => mountDialog(el, { alert$ })),\n\n  /* Header */\n  ...getComponentElements(\"header\")\n    .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n  /* Color palette */\n  ...getComponentElements(\"palette\")\n    .map(el => mountPalette(el)),\n\n  /* Search */\n  ...getComponentElements(\"search\")\n    .map(el => mountSearch(el, { index$, keyboard$ })),\n\n  /* Repository information */\n  ...getComponentElements(\"source\")\n    .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n  /* Content */\n  ...getComponentElements(\"content\")\n    .map(el => mountContent(el, { target$, viewport$, print$ })),\n\n  /* Header title */\n  ...getComponentElements(\"header-title\")\n    .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n  /* Sidebar */\n  ...getComponentElements(\"sidebar\")\n    .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n      ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n      : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n    ),\n\n  /* Navigation tabs */\n  ...getComponentElements(\"tabs\")\n    .map(el => mountTabs(el, { viewport$, header$ })),\n\n  /* Table of contents */\n  ...getComponentElements(\"toc\")\n    .map(el => mountTableOfContents(el, { viewport$, header$ })),\n\n  /* Back-to-top button */\n  ...getComponentElements(\"top\")\n    .map(el => mountBackToTop(el, { viewport$, main$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n  .pipe(\n    switchMap(() => content$),\n    mergeWith(control$),\n    shareReplay(1)\n  )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$  = document$          /* Document observable */\nwindow.location$  = location$          /* Location subject */\nwindow.target$    = target$            /* Location target observable */\nwindow.keyboard$  = keyboard$          /* Keyboard observable */\nwindow.viewport$  = viewport$          /* Viewport observable */\nwindow.tablet$    = tablet$            /* Tablet observable */\nwindow.screen$    = screen$            /* Screen observable */\nwindow.print$     = print$             /* Print mode observable */\nwindow.alert$     = alert$             /* Alert subject */\nwindow.component$ = component$         /* Component observable */\n", "import tslib from '../tslib.js';\r\nconst {\r\n    __extends,\r\n    __assign,\r\n    __rest,\r\n    __decorate,\r\n    __param,\r\n    __metadata,\r\n    __awaiter,\r\n    __generator,\r\n    __exportStar,\r\n    __createBinding,\r\n    __values,\r\n    __read,\r\n    __spread,\r\n    __spreadArrays,\r\n    __spreadArray,\r\n    __await,\r\n    __asyncGenerator,\r\n    __asyncDelegator,\r\n    __asyncValues,\r\n    __makeTemplateObject,\r\n    __importStar,\r\n    __importDefault,\r\n    __classPrivateFieldGet,\r\n    __classPrivateFieldSet,\r\n} = tslib;\r\nexport {\r\n    __extends,\r\n    __assign,\r\n    __rest,\r\n    __decorate,\r\n    __param,\r\n    __metadata,\r\n    __awaiter,\r\n    __generator,\r\n    __exportStar,\r\n    __createBinding,\r\n    __values,\r\n    __read,\r\n    __spread,\r\n    __spreadArrays,\r\n    __spreadArray,\r\n    __await,\r\n    __asyncGenerator,\r\n    __asyncDelegator,\r\n    __asyncValues,\r\n    __makeTemplateObject,\r\n    __importStar,\r\n    __importDefault,\r\n    __classPrivateFieldGet,\r\n    __classPrivateFieldSet,\r\n};\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ReplaySubject, Subject, fromEvent } from \"rxjs\"\nimport { mapTo } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject<Document> {\n  const document$ = new ReplaySubject<Document>()\n  fromEvent(document, \"DOMContentLoaded\")\n    .pipe(\n      mapTo(document)\n    )\n      .subscribe(document$)\n\n  /* Return document */\n  return document$\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getElement<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T | undefined\n\nexport function getElement<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T | undefined {\n  return node.querySelector<T>(selector) || undefined\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T\n\nexport function getElementOrThrow<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T {\n  const el = getElement<T>(selector, node)\n  if (typeof el === \"undefined\")\n    throw new ReferenceError(\n      `Missing element: expected \"${selector}\" to be present`\n    )\n  return el\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n  return document.activeElement instanceof HTMLElement\n    ? document.activeElement\n    : undefined\n}\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements<T extends keyof HTMLElementTagNameMap>(\n  selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node?: ParentNode\n): T[]\n\nexport function getElements<T extends HTMLElement>(\n  selector: string, node: ParentNode = document\n): T[] {\n  return Array.from(node.querySelectorAll<T>(selector))\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Create an element\n *\n * @template T - Tag name type\n *\n * @param tagName - Tag name\n *\n * @returns Element\n */\nexport function createElement<T extends keyof HTMLElementTagNameMap>(\n  tagName: T\n): HTMLElementTagNameMap[T] {\n  return document.createElement(tagName)\n}\n\n/**\n * Replace an element with the given list of nodes\n *\n * @param el - Element\n * @param nodes - Replacement nodes\n */\nexport function replaceElement(\n  el: HTMLElement, ...nodes: Node[]\n): void {\n  el.replaceWith(...nodes)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element focus\n *\n * @param el - Element\n * @param value - Whether the element should be focused\n */\nexport function setElementFocus(\n  el: HTMLElement, value = true\n): void {\n  if (value)\n    el.focus()\n  else\n    el.blur()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n  el: HTMLElement\n): Observable<boolean> {\n  return merge(\n    fromEvent<FocusEvent>(el, \"focus\"),\n    fromEvent<FocusEvent>(el, \"blur\")\n  )\n    .pipe(\n      map(({ type }) => type === \"focus\"),\n      startWith(el === getActiveElement())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n  width: number                        /* Element width */\n  height: number                       /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject<ResizeObserverEntry>()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n  new ResizeObserver(entries => {\n    for (const entry of entries)\n      entry$.next(entry)\n  })\n))\n  .pipe(\n    switchMap(resize => NEVER.pipe(startWith(resize))\n      .pipe(\n        finalize(() => resize.disconnect())\n      )\n    ),\n    shareReplay(1)\n  )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.offsetWidth,\n    height: el.offsetHeight\n  }\n}\n\n/**\n * Retrieve element content size, i.e. including overflowing content\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementContentSize(el: HTMLElement): ElementSize {\n  return {\n    width:  el.scrollWidth,\n    height: el.scrollHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * Sadly, we can't use the `DOMRect` objects returned by the observer, because\n * we need the emitted values to be consistent with `getElementSize`, which will\n * return the used values (rounded) and not actual values (unrounded). Thus, we\n * use the `offset*` properties. See the linked GitHub issue.\n *\n * @see https://bit.ly/3m0k3he - GitHub issue\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n  el: HTMLElement\n): Observable<ElementSize> {\n  return observer$\n    .pipe(\n      tap(observer => observer.observe(el)),\n      switchMap(observer => entry$\n        .pipe(\n          filter(({ target }) => target === el),\n          finalize(() => observer.unobserve(el)),\n          map(() => getElementSize(el))\n        )\n      ),\n      startWith(getElementSize(el))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  map,\n  startWith\n} from \"rxjs/operators\"\n\nimport {\n  getElementContentSize,\n  getElementSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(el: HTMLElement): ElementOffset {\n  return {\n    x: el.scrollLeft,\n    y: el.scrollTop\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n  el: HTMLElement\n): Observable<ElementOffset> {\n  return merge(\n    fromEvent(el, \"scroll\"),\n    fromEvent(window, \"resize\")\n  )\n    .pipe(\n      map(() => getElementOffset(el)),\n      startWith(getElementOffset(el))\n    )\n}\n\n/**\n * Watch element threshold\n *\n * This function returns an observable which emits whether the bottom scroll\n * offset of an elements is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element threshold observable\n */\nexport function watchElementThreshold(\n  el: HTMLElement, threshold = 16\n): Observable<boolean> {\n  return watchElementOffset(el)\n    .pipe(\n      map(({ y }) => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return y >= (\n          content.height - visible.height - threshold\n        )\n      }),\n      distinctUntilChanged()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set element text selection\n *\n * @param el - Element\n */\nexport function setElementSelection(\n  el: HTMLElement\n): void {\n  if (el instanceof HTMLInputElement)\n    el.select()\n  else\n    throw new Error(\"Not implemented\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\nimport { getElementOrThrow } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n  | \"drawer\"                           /* Toggle for drawer */\n  | \"search\"                           /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record<Toggle, HTMLInputElement> = {\n  drawer: getElementOrThrow(\"[data-md-toggle=drawer]\"),\n  search: getElementOrThrow(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n  return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n  if (toggles[name].checked !== value)\n    toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable<boolean> {\n  const el = toggles[name]\n  return fromEvent(el, \"change\")\n    .pipe(\n      map(() => el.checked),\n      startWith(el.checked)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { filter, map, share } from \"rxjs/operators\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n  | \"global\"                           /* Global */\n  | \"search\"                           /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n  mode: KeyboardMode                   /* Keyboard mode */\n  type: string                         /* Key type */\n  claim(): void                        /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(el: HTMLElement): boolean {\n  switch (el.tagName) {\n\n    /* Form elements */\n    case \"INPUT\":\n    case \"SELECT\":\n    case \"TEXTAREA\":\n      return true\n\n    /* Everything else */\n    default:\n      return el.isContentEditable\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable<Keyboard> {\n  return fromEvent<KeyboardEvent>(window, \"keydown\")\n    .pipe(\n      filter(ev => !(ev.metaKey || ev.ctrlKey)),\n      map(ev => ({\n        mode: getToggle(\"search\") ? \"search\" : \"global\",\n        type: ev.key,\n        claim() {\n          ev.preventDefault()\n          ev.stopPropagation()\n        }\n      } as Keyboard)),\n      filter(({ mode }) => {\n        if (mode === \"global\") {\n          const active = getActiveElement()\n          if (typeof active !== \"undefined\")\n            return !isSusceptibleToKeyboard(active)\n        }\n        return true\n      }),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n  return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n  location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject<URL> {\n  return new Subject<URL>()\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport { filter, map, share, startWith, switchMap } from \"rxjs/operators\"\n\nimport { createElement, getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n  return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n  const el = createElement(\"a\")\n  el.href = hash\n  el.addEventListener(\"click\", ev => ev.stopPropagation())\n  el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable<string> {\n  return fromEvent<HashChangeEvent>(window, \"hashchange\")\n    .pipe(\n      map(getLocationHash),\n      startWith(getLocationHash()),\n      filter(hash => hash.length > 0),\n      share()\n    )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable<HTMLElement> {\n  return watchLocationHash()\n    .pipe(\n      switchMap(id => of(getElement(`[id=\"${id}\"]`)!))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, fromEvent, merge } from \"rxjs\"\nimport {\n  filter,\n  map,\n  mapTo,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable<boolean> {\n  const media = matchMedia(query)\n  return fromEvent<MediaQueryListEvent>(media, \"change\")\n    .pipe(\n      map(ev => ev.matches),\n      startWith(media.matches)\n    )\n}\n\n/**\n * Watch print mode, cross-browser\n *\n * @returns Print mode observable\n */\nexport function watchPrint(): Observable<void> {\n  return merge(\n    watchMedia(\"print\").pipe(filter(Boolean)),  /* Webkit */\n    fromEvent(window, \"beforeprint\")            /* IE, FF */\n  )\n    .pipe(\n      mapTo(undefined)\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at<T>(\n  query$: Observable<boolean>, factory: () => Observable<T>\n): Observable<T> {\n  return query$\n    .pipe(\n      switchMap(active => active ? factory() : NEVER)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, from } from \"rxjs\"\nimport {\n  filter,\n  map,\n  shareReplay,\n  switchMap\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n  url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable<Response> {\n  return from(fetch(`${url}`, options))\n    .pipe(\n      filter(res => res.status === 200),\n    )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON<T>(\n  url: URL | string, options?: RequestInit\n): Observable<T> {\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.json()),\n      shareReplay(1)\n    )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n  url: URL | string, options?: RequestInit\n): Observable<Document> {\n  const dom = new DOMParser()\n  return request(url, options)\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/xml\")),\n      shareReplay(1)\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, merge } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n  x: number                            /* Horizontal offset */\n  y: number                            /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n  return {\n    x: Math.max(0, pageXOffset),\n    y: Math.max(0, pageYOffset)\n  }\n}\n\n/**\n * Set viewport offset\n *\n * @param offset - Viewport offset\n */\nexport function setViewportOffset(\n  { x, y }: Partial<ViewportOffset>\n): void {\n  window.scrollTo(x || 0, y || 0)\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable<ViewportOffset> {\n  return merge(\n    fromEvent(window, \"scroll\", { passive: true }),\n    fromEvent(window, \"resize\", { passive: true })\n  )\n    .pipe(\n      map(getViewportOffset),\n      startWith(getViewportOffset())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent } from \"rxjs\"\nimport { map, startWith } from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n  width: number                        /* Viewport width */\n  height: number                       /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n  return {\n    width:  innerWidth,\n    height: innerHeight\n  }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable<ViewportSize> {\n  return fromEvent(window, \"resize\", { passive: true })\n    .pipe(\n      map(getViewportSize),\n      startWith(getViewportSize())\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, combineLatest } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  map,\n  shareReplay\n} from \"rxjs/operators\"\n\nimport { Header } from \"~/components\"\n\nimport {\n  ViewportOffset,\n  watchViewportOffset\n} from \"../offset\"\nimport {\n  ViewportSize,\n  watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n  offset: ViewportOffset               /* Viewport offset */\n  size: ViewportSize                   /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch at options\n */\ninterface WatchAtOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable<Viewport> {\n  return combineLatest([\n    watchViewportOffset(),\n    watchViewportSize()\n  ])\n    .pipe(\n      map(([offset, size]) => ({ offset, size })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n  el: HTMLElement, { viewport$, header$ }: WatchAtOptions\n): Observable<Viewport> {\n  const size$ = viewport$\n    .pipe(\n      distinctUntilKeyChanged(\"size\")\n    )\n\n  /* Compute element offset */\n  const offset$ = combineLatest([size$, header$])\n    .pipe(\n      map((): ViewportOffset => ({\n        x: el.offsetLeft,\n        y: el.offsetTop\n      }))\n    )\n\n  /* Compute relative viewport, return hot observable */\n  return combineLatest([header$, viewport$, offset$])\n    .pipe(\n      map(([{ height }, { offset, size }, { x, y }]) => ({\n        offset: {\n          x: offset.x - x,\n          y: offset.y - y + height\n        },\n        size\n      }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, fromEvent } from \"rxjs\"\nimport {\n  map,\n  share,\n  switchMapTo,\n  tap,\n  throttle\n} from \"rxjs/operators\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n  type: unknown                        /* Message type */\n  data?: unknown                       /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n  T extends WorkerMessage\n> {\n  tx$: Subject<T>                      /* Message transmission subject */\n  rx$: Observable<T>                   /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions<T extends WorkerMessage> {\n  tx$: Observable<T>                   /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker<T extends WorkerMessage>(\n  worker: Worker, { tx$ }: WatchOptions<T>\n): Observable<T> {\n\n  /* Intercept messages from worker-like objects */\n  const rx$ = fromEvent<MessageEvent>(worker, \"message\")\n    .pipe(\n      map(({ data }) => data as T)\n    )\n\n  /* Send and receive messages, return hot observable */\n  return tx$\n    .pipe(\n      throttle(() => rx$, { leading: true, trailing: true }),\n      tap(message => worker.postMessage(message)),\n      switchMapTo(rx$),\n      share()\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n  | \"header.autohide\"                  /* Hide header */\n  | \"navigation.expand\"                /* Automatic expansion */\n  | \"navigation.instant\"               /* Instant loading */\n  | \"navigation.sections\"              /* Sections navigation */\n  | \"navigation.tabs\"                  /* Tabs navigation */\n  | \"navigation.top\"                   /* Back-to-top button */\n  | \"toc.integrate\"                    /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n  | \"clipboard.copy\"                   /* Copy to clipboard */\n  | \"clipboard.copied\"                 /* Copied to clipboard */\n  | \"search.config.lang\"               /* Search language */\n  | \"search.config.pipeline\"           /* Search pipeline */\n  | \"search.config.separator\"          /* Search separator */\n  | \"search.placeholder\"               /* Search */\n  | \"search.result.placeholder\"        /* Type to start searching */\n  | \"search.result.none\"               /* No matching documents */\n  | \"search.result.one\"                /* 1 matching document */\n  | \"search.result.other\"              /* # matching documents */\n  | \"search.result.more.one\"           /* 1 more on this page */\n  | \"search.result.more.other\"         /* # more on this page */\n  | \"search.result.term.missing\"       /* Missing */\n\n/**\n * Translations\n */\nexport type Translations = Record<Translation, string>\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n  provider: \"mike\"                     /* Version provider */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n  base: string                         /* Base URL */\n  features: Flag[]                     /* Feature flags */\n  translations: Translations           /* Translations */\n  search: string                       /* Search worker URL */\n  version?: Versioning                 /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElementOrThrow(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = new URL(config.base, getLocation())\n  .toString()\n  .replace(/\\/$/, \"\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n  return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n  return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n  key: Translation, value?: string | number\n): string {\n  return typeof value !== \"undefined\"\n    ? config.translations[key].replace(\"#\", value.toString())\n    : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElementOrThrow, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component\n */\nexport type ComponentType =\n  | \"announce\"                         /* Announcement bar */\n  | \"container\"                        /* Container */\n  | \"content\"                          /* Content */\n  | \"dialog\"                           /* Dialog */\n  | \"header\"                           /* Header */\n  | \"header-title\"                     /* Header title */\n  | \"header-topic\"                     /* Header topic */\n  | \"main\"                             /* Main area */\n  | \"palette\"                          /* Color palette */\n  | \"search\"                           /* Search */\n  | \"search-query\"                     /* Search input */\n  | \"search-result\"                    /* Search results */\n  | \"sidebar\"                          /* Sidebar */\n  | \"skip\"                             /* Skip link */\n  | \"source\"                           /* Repository information */\n  | \"tabs\"                             /* Navigation tabs */\n  | \"toc\"                              /* Table of contents */\n  | \"top\"                              /* Back-to-top button */\n\n/**\n * A component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n  T extends {} = {},\n  U extends HTMLElement = HTMLElement\n> =\n  T & {\n    ref: U                             /* Component reference */\n  }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n  \"announce\": HTMLElement              /* Announcement bar */\n  \"container\": HTMLElement             /* Container */\n  \"content\": HTMLElement               /* Content */\n  \"dialog\": HTMLElement                /* Dialog */\n  \"header\": HTMLElement                /* Header */\n  \"header-title\": HTMLElement          /* Header title */\n  \"header-topic\": HTMLElement          /* Header topic */\n  \"main\": HTMLElement                  /* Main area */\n  \"palette\": HTMLElement               /* Color palette */\n  \"search\": HTMLElement                /* Search */\n  \"search-query\": HTMLInputElement     /* Search input */\n  \"search-result\": HTMLElement         /* Search results */\n  \"sidebar\": HTMLElement               /* Sidebar */\n  \"skip\": HTMLAnchorElement            /* Skip link */\n  \"source\": HTMLAnchorElement          /* Repository information */\n  \"tabs\": HTMLElement                  /* Navigation tabs */\n  \"toc\": HTMLElement                   /* Table of contents */\n  \"top\": HTMLAnchorElement             /* Back-to-top button */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n  return getElementOrThrow(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements<T extends ComponentType>(\n  type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n  return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  switchMap,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetFocusable, setFocusable } from \"~/actions\"\nimport {\n  Viewport,\n  getElementContentSize,\n  getElementSize,\n  getElements,\n  watchMedia\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n  scroll: boolean                      /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global index for Clipboard.js integration\n */\nlet index = 0\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n  el: HTMLElement, { viewport$ }: WatchOptions\n): Observable<CodeBlock> {\n  const container$ = of(el)\n    .pipe(\n      switchMap(child => {\n        const container = child.closest(\"[data-tabs]\")\n        if (container instanceof HTMLElement) {\n          return merge(\n            ...getElements(\"input\", container)\n              .map(input => fromEvent(input, \"change\"))\n          )\n        }\n        return NEVER\n      })\n    )\n\n  /* Check overflow on resize and tab change */\n  return merge(\n    viewport$.pipe(distinctUntilKeyChanged(\"size\")),\n    container$\n  )\n    .pipe(\n      map(() => {\n        const visible = getElementSize(el)\n        const content = getElementContentSize(el)\n        return {\n          scroll: content.width > visible.width\n        }\n      }),\n      distinctUntilKeyChanged(\"scroll\")\n    )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block component observable\n */\nexport function mountCodeBlock(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<CodeBlock>> {\n  const internal$ = new Subject<CodeBlock>()\n  internal$\n    .pipe(\n      withLatestFrom(watchMedia(\"(hover)\"))\n    )\n      .subscribe(([{ scroll }, hover]) => {\n        if (scroll && hover)\n          setFocusable(el)\n        else\n          resetFocusable(el)\n      })\n\n  /* Render button for Clipboard.js integration */\n  if (ClipboardJS.isSupported()) {\n    const parent = el.closest(\"pre\")!\n    parent.id = `__code_${index++}`\n    parent.insertBefore(\n      renderClipboardButton(parent.id),\n      el\n    )\n  }\n\n  /* Create and return component */\n  return watchCodeBlock(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set focusable property\n *\n * @param el - Element\n * @param value - Tabindex value\n */\nexport function setFocusable(\n  el: HTMLElement, value = 0\n): void {\n  el.setAttribute(\"tabindex\", value.toString())\n}\n\n/**\n * Reset focusable property\n *\n * @param el - Element\n */\nexport function resetFocusable(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"tabindex\")\n}\n\n/**\n * Set scroll lock\n *\n * @param el - Scrollable element\n * @param value - Vertical offset\n */\nexport function setScrollLock(\n  el: HTMLElement, value: number\n): void {\n  el.setAttribute(\"data-md-state\", \"lock\")\n  el.style.top = `-${value}px`\n}\n\n/**\n * Reset scroll lock\n *\n * @param el - Scrollable element\n */\nexport function resetScrollLock(\n  el: HTMLElement\n): void {\n  const value = -1 * parseInt(el.style.top, 10)\n  el.removeAttribute(\"data-md-state\")\n  el.style.top = \"\"\n  if (value)\n    window.scrollTo(0, value)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set anchor state\n *\n * @param el - Anchor element\n * @param state - Anchor state\n */\nexport function setAnchorState(\n  el: HTMLElement, state: \"blur\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset anchor state\n *\n * @param el - Anchor element\n */\nexport function resetAnchorState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set anchor active\n *\n * @param el - Anchor element\n * @param value - Whether the anchor is active\n */\nexport function setAnchorActive(\n  el: HTMLElement, value: boolean\n): void {\n  el.classList.toggle(\"md-nav__link--active\", value)\n}\n\n/**\n * Reset anchor active\n *\n * @param el - Anchor element\n */\nexport function resetAnchorActive(\n  el: HTMLElement\n): void {\n  el.classList.remove(\"md-nav__link--active\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set dialog message\n *\n * @param el - Dialog element\n * @param value - Dialog message\n */\nexport function setDialogMessage(\n  el: HTMLElement, value: string\n): void {\n  el.firstElementChild!.innerHTML = value\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set dialog state\n *\n * @param el - Dialog element\n * @param state - Dialog state\n */\nexport function setDialogState(\n  el: HTMLElement, state: \"open\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset dialog state\n *\n * @param el - Dialog element\n */\nexport function resetDialogState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header state\n *\n * @param el - Header element\n * @param state - Header state\n */\nexport function setHeaderState(\n  el: HTMLElement, state: \"shadow\" | \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header state\n *\n * @param el - Header element\n */\nexport function resetHeaderState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set header title state\n *\n * @param el - Header title element\n * @param state - Header title state\n */\nexport function setHeaderTitleState(\n  el: HTMLElement, state: \"active\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset header title state\n *\n * @param el - Header title element\n */\nexport function resetHeaderTitleState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set search query placeholder\n *\n * @param el - Search query element\n * @param value - Placeholder\n */\nexport function setSearchQueryPlaceholder(\n  el: HTMLInputElement, value: string\n): void {\n  el.placeholder = value\n}\n\n/**\n * Reset search query placeholder\n *\n * @param el - Search query element\n */\nexport function resetSearchQueryPlaceholder(\n  el: HTMLInputElement\n): void {\n  el.placeholder = translation(\"search.placeholder\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n  & JSXInternal.HTMLAttributes\n  & JSXInternal.SVGAttributes\n  & Record<string, any>\n\n/**\n * Child element\n */\ntype Child =\n  | HTMLElement\n  | Text\n  | string\n  | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n  /* Handle primitive types (including raw HTML) */\n  if (typeof child === \"string\" || typeof child === \"number\") {\n    el.innerHTML += child.toString()\n\n  /* Handle nodes */\n  } else if (child instanceof Node) {\n    el.appendChild(child)\n\n  /* Handle nested children */\n  } else if (Array.isArray(child)) {\n    for (const node of child)\n      appendChild(el, node)\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n  tag: string, attributes: Attributes | null, ...children: Child[]\n): HTMLElement {\n  const el = document.createElement(tag)\n\n  /* Set attributes, if any */\n  if (attributes)\n    for (const attr of Object.keys(attributes))\n      if (typeof attributes[attr] !== \"boolean\")\n        el.setAttribute(attr, attributes[attr])\n      else if (attributes[attr])\n        el.setAttribute(attr, \"\")\n\n  /* Append child nodes */\n  for (const child of children)\n    appendChild(el, child)\n\n  /* Return element */\n  return el\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n  namespace JSX {\n    type Element = HTMLElement\n    type IntrinsicElements = JSXInternal.IntrinsicElements\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n  let i = n\n  if (value.length > i) {\n    while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n    return `${value.substring(0, i)}...`\n  }\n  return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n  if (value > 999) {\n    const digits = +((value - 950) % 1000 > 99)\n    return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n  } else {\n    return value.toString()\n  }\n}\n\n/**\n * Simple hash function\n *\n * @see https://bit.ly/2wsVjJ4 - Original source\n *\n * @param value - Value to be hashed\n *\n * @returns Hash as 32bit integer\n */\nexport function hash(value: string): number {\n  let h = 0\n  for (let i = 0, len = value.length; i < len; i++) {\n    h  = ((h << 5) - h) + value.charCodeAt(i)\n    h |= 0 // Convert to 32bit integer\n  }\n  return h\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set number of search results\n *\n * @param el - Search result metadata element\n * @param value - Number of results\n */\nexport function setSearchResultMeta(\n  el: HTMLElement, value: number\n): void {\n  switch (value) {\n\n    /* No results */\n    case 0:\n      el.textContent = translation(\"search.result.none\")\n      break\n\n    /* One result */\n    case 1:\n      el.textContent = translation(\"search.result.one\")\n      break\n\n    /* Multiple result */\n    default:\n      el.textContent = translation(\"search.result.other\", round(value))\n  }\n}\n\n/**\n * Reset number of search results\n *\n * @param el - Search result metadata element\n */\nexport function resetSearchResultMeta(\n  el: HTMLElement\n): void {\n  el.textContent = translation(\"search.result.placeholder\")\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Add an element to the search result list\n *\n * @param el - Search result list element\n * @param child - Search result element\n */\nexport function addToSearchResultList(\n  el: HTMLElement, child: Element\n): void {\n  el.appendChild(child)\n}\n\n/**\n * Reset search result list\n *\n * @param el - Search result list element\n */\nexport function resetSearchResultList(\n  el: HTMLElement\n): void {\n  el.innerHTML = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar offset\n *\n * @param el - Sidebar element\n * @param value - Sidebar offset\n */\nexport function setSidebarOffset(\n  el: HTMLElement, value: number\n): void {\n  el.style.top = `${value}px`\n}\n\n/**\n * Reset sidebar offset\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarOffset(\n  el: HTMLElement\n): void {\n  el.style.top = \"\"\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Set sidebar height\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param value - Sidebar height\n */\nexport function setSidebarHeight(\n  el: HTMLElement, value: number\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`\n}\n\n/**\n * Reset sidebar height\n *\n * @param el - Sidebar element\n */\nexport function resetSidebarHeight(\n  el: HTMLElement\n): void {\n  const scrollwrap = el.firstElementChild as HTMLElement\n  scrollwrap.style.height = \"\"\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set repository facts\n *\n * @param el - Repository element\n * @param child - Repository facts element\n */\nexport function setSourceFacts(\n  el: HTMLElement, child: Element\n): void {\n  el.lastElementChild!.appendChild(child)\n}\n\n/**\n * Set repository state\n *\n * @param el - Repository element\n * @param state - Repository state\n */\nexport function setSourceState(\n  el: HTMLElement, state: \"done\"\n): void {\n  el.lastElementChild!.setAttribute(\"data-md-state\", state)\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set tabs state\n *\n * @param el - Tabs element\n * @param state - Tabs state\n */\nexport function setTabsState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset tabs state\n *\n * @param el - Tabs element\n */\nexport function resetTabsState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set back-to-top state\n *\n * @param el - Back-to-top element\n * @param state - Back-to-top state\n */\nexport function setBackToTopState(\n  el: HTMLElement, state: \"hidden\"\n): void {\n  el.setAttribute(\"data-md-state\", state)\n}\n\n/**\n * Reset back-to-top state\n *\n * @param el - Back-to-top element\n */\nexport function resetBackToTopState(\n  el: HTMLElement\n): void {\n  el.removeAttribute(\"data-md-state\")\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n  return (\n    <button\n      class=\"md-clipboard md-icon\"\n      title={translation(\"clipboard.copy\")}\n      data-clipboard-target={`#${id} > code`}\n    ></button>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport {\n  SearchDocument,\n  SearchMetadata,\n  SearchResult\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n  TEASER = 1,                          /* Render teaser */\n  PARENT = 2                           /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n  document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n  const parent = flag & Flag.PARENT\n  const teaser = flag & Flag.TEASER\n\n  /* Render missing query terms */\n  const missing = Object.keys(document.terms)\n    .filter(key => !document.terms[key])\n    .map(key => [<del>{key}</del>, \" \"])\n    .flat()\n    .slice(0, -1)\n\n  /* Render article or section, depending on flags */\n  const url = document.location\n  return (\n    <a href={url} class=\"md-search-result__link\" tabIndex={-1}>\n      <article\n        class={[\"md-search-result__article\", ...parent\n          ? [\"md-search-result__article--document\"]\n          : []\n        ].join(\" \")}\n        data-md-score={document.score.toFixed(2)}\n      >\n        {parent > 0 && <div class=\"md-search-result__icon md-icon\"></div>}\n        <h1 class=\"md-search-result__title\">{document.title}</h1>\n        {teaser > 0 && document.text.length > 0 &&\n          <p class=\"md-search-result__teaser\">\n            {truncate(document.text, 320)}\n          </p>\n        }\n        {teaser > 0 && missing.length > 0 &&\n          <p class=\"md-search-result__terms\">\n            {translation(\"search.result.term.missing\")}: {...missing}\n          </p>\n        }\n      </article>\n    </a>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResult(\n  result: SearchResult\n): HTMLElement {\n  const threshold = result[0].score\n  const docs = [...result]\n\n  /* Find and extract parent article */\n  const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n  const [article] = docs.splice(parent, 1)\n\n  /* Determine last index above threshold */\n  let index = docs.findIndex(doc => doc.score < threshold)\n  if (index === -1)\n    index = docs.length\n\n  /* Partition sections */\n  const best = docs.slice(0, index)\n  const more = docs.slice(index)\n\n  /* Render children */\n  const children = [\n    renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n    ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n    ...more.length ? [\n      <details class=\"md-search-result__more\">\n        <summary tabIndex={-1}>\n          {more.length > 0 && more.length === 1\n            ? translation(\"search.result.more.one\")\n            : translation(\"search.result.more.other\", more.length)\n          }\n        </summary>\n        {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n      </details>\n    ] : []\n  ]\n\n  /* Render search result */\n  return (\n    <li class=\"md-search-result__item\">\n      {children}\n    </li>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h, round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n  return (\n    <ul class=\"md-source__facts\">\n      {Object.entries(facts).map(([key, value]) => (\n        <li class={`md-source__fact md-source__fact--${key}`}>\n          {typeof value === \"number\" ? round(value) : value}\n        </li>\n      ))}\n    </ul>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n  return (\n    <div class=\"md-typeset__scrollwrap\">\n      <div class=\"md-typeset__table\">\n        {table}\n      </div>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n  version: string                      /* Version identifier */\n  title: string                        /* Version title */\n  aliases: string[]                    /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version\n *\n * @param version - Version\n *\n * @returns Element\n */\nfunction renderVersion(version: Version): HTMLElement {\n  const config = configuration()\n\n  /* Ensure trailing slash, see https://bit.ly/3rL5u3f */\n  const url = new URL(`${version.version}/`, config.base)\n  return (\n    <li class=\"md-version__item\">\n      <a href={url.toString()} class=\"md-version__link\">\n        {version.title}\n      </a>\n    </li>\n  )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n *\n * @returns Element\n */\nexport function renderVersionSelector(versions: Version[]): HTMLElement {\n  const config = configuration()\n\n  /* Determine active version */\n  const [, current] = config.base.match(/([^/]+)\\/?$/)!\n  const active =\n    versions.find(({ version, aliases }) => (\n      version === current || aliases.includes(current)\n    )) || versions[0]\n\n  /* Render version selector */\n  return (\n    <div class=\"md-version\">\n      <span class=\"md-version__current\">\n        {active.title}\n      </span>\n      <ul class=\"md-version__list\">\n        {versions.map(renderVersion)}\n      </ul>\n    </div>\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject } from \"rxjs\"\nimport {\n  filter,\n  finalize,\n  map,\n  mapTo,\n  mergeWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n  el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable<Details> {\n  return target$\n    .pipe(\n      map(target => target.closest(\"details:not([open])\")!),\n      filter(details => el === details),\n      mergeWith(print$),\n      mapTo(el)\n    )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n  el: HTMLDetailsElement, options: MountOptions\n): Observable<Component<Details>> {\n  const internal$ = new Subject<Details>()\n  internal$.subscribe(() => {\n    el.setAttribute(\"open\", \"\")\n    el.scrollIntoView()\n  })\n\n  /* Create and return component */\n  return watchDetails(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      mapTo({ ref: el })\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { createElement, replaceElement } from \"~/browser\"\nimport { renderTable } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = createElement(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n  el: HTMLElement\n): Observable<Component<DataTable>> {\n  replaceElement(el, sentinel)\n  replaceElement(sentinel, renderTable(el))\n\n  /* Create and return component */\n  return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { Viewport, getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { CodeBlock, mountCodeBlock } from \"../code\"\nimport { Details, mountDetails } from \"../details\"\nimport { DataTable, mountDataTable } from \"../table\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n  | CodeBlock\n  | DataTable\n  | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  target$: Observable<HTMLElement>     /* Location target observable */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  print$: Observable<void>             /* Print mode observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n  el: HTMLElement, { target$, viewport$, print$ }: MountOptions\n): Observable<Component<Content>> {\n  return merge(\n\n    /* Code blocks */\n    ...getElements(\"pre > code\", el)\n      .map(child => mountCodeBlock(child, { viewport$ })),\n\n    /* Data tables */\n    ...getElements(\"table:not([class])\", el)\n      .map(child => mountDataTable(child)),\n\n    /* Details */\n    ...getElements(\"details\", el)\n      .map(child => mountDetails(child, { target$, print$ }))\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetDialogState,\n  setDialogMessage,\n  setDialogState\n} from \"~/actions\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n  message: string                      /* Dialog message */\n  open: boolean                        /* Dialog is visible */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n  _el: HTMLElement, { alert$ }: WatchOptions\n): Observable<Dialog> {\n  return alert$\n    .pipe(\n      switchMap(message => merge(\n        of(true),\n        of(false).pipe(delay(2000))\n      )\n        .pipe(\n          map(open => ({ message, open }))\n        )\n      )\n    )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right cornerwhen a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Dialog>> {\n  const internal$ = new Subject<Dialog>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ message, open }) => {\n        setDialogMessage(el, message)\n        if (open)\n          setDialogState(el, \"open\")\n        else\n          resetDialogState(el)\n      })\n\n  /* Create and return component */\n  return watchDialog(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest,\n  defer,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  combineLatestWith,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  observeOn,\n  shareReplay,\n  startWith,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { feature } from \"~/_\"\nimport { resetHeaderState, setHeaderState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n  height: number                       /* Header visible height */\n  sticky: boolean                      /* Header stickyness */\n  hidden: boolean                      /* User scrolled past threshold */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable<boolean> {\n  if (!feature(\"header.autohide\"))\n    return of(false)\n\n  /* Compute direction and turning point */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => [a < b, b] as const),\n      distinctUntilKeyChanged(0)\n    )\n\n  /* Compute whether header should be hidden */\n  const hidden$ = combineLatest([viewport$, direction$])\n    .pipe(\n      filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n      map(([, [direction]]) => direction),\n      distinctUntilChanged()\n    )\n\n  /* Compute threshold for hiding */\n  const search$ = watchToggle(\"search\")\n  return combineLatest([viewport$, search$])\n    .pipe(\n      map(([{ offset }, search]) => offset.y > 400 && !search),\n      distinctUntilChanged(),\n      switchMap(active => active ? hidden$ : of(false)),\n      startWith(false)\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n  el: HTMLElement, options: WatchOptions\n): Observable<Header> {\n  return defer(() => {\n    const styles = getComputedStyle(el)\n    return of(\n      styles.position === \"sticky\" ||\n      styles.position === \"-webkit-sticky\"\n    )\n  })\n    .pipe(\n      combineLatestWith(watchElementSize(el), isHidden(options)),\n      map(([sticky, { height }, hidden]) => ({\n        height: sticky ? height : 0,\n        sticky,\n        hidden\n      })),\n      distinctUntilChanged((a, b) => (\n        a.sticky === b.sticky &&\n        a.height === b.height &&\n        a.hidden === b.hidden\n      )),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n  el: HTMLElement, { header$, main$ }: MountOptions\n): Observable<Component<Header>> {\n  const internal$ = new Subject<Main>()\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"active\"),\n      combineLatestWith(header$),\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(([{ active }, { hidden }]) => {\n        if (active)\n          setHeaderState(el, hidden ? \"hidden\" : \"shadow\")\n        else\n          resetHeaderState(el)\n      })\n\n  /* Connect to long-living subject and return component */\n  main$.subscribe(main => internal$.next(main))\n  return header$\n    .pipe(\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  NEVER,\n  Observable,\n  Subject,\n  animationFrameScheduler\n} from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetHeaderTitleState,\n  setHeaderTitleState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n  active: boolean                      /* User scrolled past first headline */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n  el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions\n): Observable<HeaderTitle> {\n  return watchViewportAt(el, { header$, viewport$ })\n    .pipe(\n      map(({ offset: { y } }) => {\n        const { height } = getElementSize(el)\n        return {\n          active: y >= height\n        }\n      }),\n      distinctUntilKeyChanged(\"active\")\n    )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<HeaderTitle>> {\n  const internal$ = new Subject<HeaderTitle>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe(({ active }) => {\n        if (active)\n          setHeaderTitleState(el, \"active\")\n        else\n          resetHeaderTitleState(el)\n      })\n\n  /* Obtain headline, if any */\n  const headline = getElement<HTMLHeadingElement>(\"article h1\")\n  if (typeof headline === \"undefined\")\n    return NEVER\n\n  /* Create and return component */\n  return watchHeaderTitle(headline, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  map,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { Viewport, watchElementSize } from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n  offset: number                       /* Main area top offset */\n  height: number                       /* Main area visible height */\n  active: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Main> {\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(({ height }) => height),\n      distinctUntilChanged()\n    )\n\n  /* Compute the main area's top and bottom borders */\n  const border$ = adjust$\n    .pipe(\n      switchMap(() => watchElementSize(el)\n        .pipe(\n          map(({ height }) => ({\n            top:    el.offsetTop,\n            bottom: el.offsetTop + height\n          })),\n          distinctUntilKeyChanged(\"bottom\")\n        )\n      )\n    )\n\n  /* Compute the main area's offset, visible height and if we scrolled past */\n  return combineLatest([adjust$, border$, viewport$])\n    .pipe(\n      map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n        height = Math.max(0, height\n          - Math.max(0, top    - y,  header)\n          - Math.max(0, height + y - bottom)\n        )\n        return {\n          offset: top - header,\n          height,\n          active: top - header <= y\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.offset === b.offset &&\n        a.height === b.height &&\n        a.active === b.active\n      ))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  fromEvent,\n  of\n} from \"rxjs\"\nimport {\n  finalize,\n  map,\n  mapTo,\n  mergeMap,\n  shareReplay,\n  startWith,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Palette colors\n */\nexport interface PaletteColor {\n  scheme?: string                      /* Color scheme */\n  primary?: string                     /* Primary color */\n  accent?: string                      /* Accent color */\n}\n\n/**\n * Palette\n */\nexport interface Palette {\n  index: number                        /* Palette index */\n  color: PaletteColor                  /* Palette colors */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch color palette\n *\n * @param inputs - Color palette element\n *\n * @returns Color palette observable\n */\nexport function watchPalette(\n  inputs: HTMLInputElement[]\n): Observable<Palette> {\n  const data = localStorage.getItem(__prefix(\"__palette\"))!\n  const current = JSON.parse(data) || {\n    index: inputs.findIndex(input => (\n      matchMedia(input.getAttribute(\"data-md-color-media\")!).matches\n    ))\n  }\n\n  /* Emit changes in color palette */\n  const palette$ = of(...inputs)\n    .pipe(\n      mergeMap(input => fromEvent(input, \"change\")\n        .pipe(\n          mapTo(input)\n        )\n      ),\n      startWith(inputs[Math.max(0, current.index)]),\n      map(input => ({\n        index: inputs.indexOf(input),\n        color: {\n          scheme:  input.getAttribute(\"data-md-color-scheme\"),\n          primary: input.getAttribute(\"data-md-color-primary\"),\n          accent:  input.getAttribute(\"data-md-color-accent\")\n        }\n      } as Palette)),\n      shareReplay(1)\n    )\n\n  /* Persist preference in local storage */\n  palette$.subscribe(palette => {\n    localStorage.setItem(__prefix(\"__palette\"), JSON.stringify(palette))\n  })\n\n  /* Return palette */\n  return palette$\n}\n\n/**\n * Mount color palette\n *\n * @param el - Color palette element\n *\n * @returns Color palette component observable\n */\nexport function mountPalette(\n  el: HTMLElement\n): Observable<Component<Palette>> {\n  const internal$ = new Subject<Palette>()\n\n  /* Set color palette */\n  internal$.subscribe(palette => {\n    for (const [key, value] of Object.entries(palette.color))\n      if (typeof value === \"string\")\n        document.body.setAttribute(`data-md-color-${key}`, value)\n\n    /* Toggle visibility */\n    for (let index = 0; index < inputs.length; index++) {\n      const label = inputs[index].nextElementSibling as HTMLElement\n      label.hidden = palette.index !== index\n    }\n  })\n\n  /* Create and return component */\n  const inputs = getElements<HTMLInputElement>(\"input\", el)\n  return watchPalette(inputs)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport { Observable, Subject } from \"rxjs\"\n\nimport { translation } from \"~/_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  alert$: Subject<string>              /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n  { alert$ }: SetupOptions\n): void {\n  if (ClipboardJS.isSupported()) {\n    new Observable<ClipboardJS.Event>(subscriber => {\n      new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\")\n        .on(\"success\", ev => subscriber.next(ev))\n    })\n      .subscribe(() => alert$.next(translation(\"clipboard.copied\")))\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  EMPTY,\n  NEVER,\n  Observable,\n  Subject,\n  fromEvent,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  catchError,\n  concatMap,\n  debounceTime,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  filter,\n  map,\n  sample,\n  share,\n  skip,\n  skipUntil,\n  switchMap\n} from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Viewport,\n  ViewportOffset,\n  createElement,\n  getElement,\n  getElements,\n  replaceElement,\n  request,\n  requestXML,\n  setLocation,\n  setLocationHash,\n  setViewportOffset\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n  url: URL                             /* State URL */\n  offset?: ViewportOffset              /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n  document$: Subject<Document>         /* Document subject */\n  location$: Subject<URL>              /* Location subject */\n  viewport$: Observable<Viewport>      /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns Processed URLs\n */\nfunction preprocess(urls: string[]): string[] {\n  if (urls.length < 2)\n    return urls\n\n  /* Take the first two URLs and remove everything after the last slash */\n  const [root, next] = urls\n    .sort((a, b) => a.length - b.length)\n    .map(url => url.replace(/[^/]+$/, \"\"))\n\n  /* Compute common prefix */\n  let index = 0\n  if (root === next)\n    index = root.length\n  else\n    while (root.charCodeAt(index) === next.charCodeAt(index))\n      index++\n\n  /* Replace common prefix (i.e. base) with effective base */\n  const config = configuration()\n  return urls.map(url => (\n    url.replace(root.slice(0, index), `${config.base}/`)\n  ))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n  { document$, location$, viewport$ }: SetupOptions\n): void {\n  const config = configuration()\n  if (location.protocol === \"file:\")\n    return\n\n  /* Disable automatic scroll restoration */\n  if (\"scrollRestoration\" in history) {\n    history.scrollRestoration = \"manual\"\n\n    /* Hack: ensure that reloads restore viewport offset */\n    fromEvent(window, \"beforeunload\")\n      .subscribe(() => {\n        history.scrollRestoration = \"auto\"\n      })\n  }\n\n  /* Hack: ensure absolute favicon link to omit 404s when switching */\n  const favicon = getElement<HTMLLinkElement>(\"link[rel=icon]\")\n  if (typeof favicon !== \"undefined\")\n    favicon.href = favicon.href\n\n  /* Intercept internal navigation */\n  const push$ = requestXML(`${config.base}/sitemap.xml`)\n    .pipe(\n      map(sitemap => preprocess(getElements(\"loc\", sitemap)\n        .map(node => node.textContent!)\n      )),\n      switchMap(urls => fromEvent<MouseEvent>(document.body, \"click\")\n        .pipe(\n          filter(ev => !ev.metaKey && !ev.ctrlKey),\n          switchMap(ev => {\n\n            /* Handle HTML and SVG elements */\n            if (ev.target instanceof Element) {\n              const el = ev.target.closest(\"a\")\n              if (el && !el.target && urls.includes(el.href)) {\n                ev.preventDefault()\n                return of({\n                  url: new URL(el.href)\n                })\n              }\n            }\n            return NEVER\n          })\n        )\n      ),\n      share<HistoryState>()\n    )\n\n  /* Intercept history back and forward */\n  const pop$ = fromEvent<PopStateEvent>(window, \"popstate\")\n    .pipe(\n      filter(ev => ev.state !== null),\n      map(ev => ({\n        url: new URL(location.href),\n        offset: ev.state\n      })),\n      share<HistoryState>()\n    )\n\n  /* Emit location change */\n  merge(push$, pop$)\n    .pipe(\n      distinctUntilChanged((a, b) => a.url.href === b.url.href),\n      map(({ url }) => url)\n    )\n      .subscribe(location$)\n\n  /* Fetch document via `XMLHTTPRequest` */\n  const response$ = location$\n    .pipe(\n      distinctUntilKeyChanged(\"pathname\"),\n      switchMap(url => request(url.href)\n        .pipe(\n          catchError(() => {\n            setLocation(url)\n            return NEVER\n          })\n        )\n      ),\n      share()\n    )\n\n  /* Set new location via `history.pushState` */\n  push$\n    .pipe(\n      sample(response$)\n    )\n      .subscribe(({ url }) => {\n        history.pushState({}, \"\", `${url}`)\n      })\n\n  /* Parse and emit fetched document */\n  const dom = new DOMParser()\n  response$\n    .pipe(\n      switchMap(res => res.text()),\n      map(res => dom.parseFromString(res, \"text/html\"))\n    )\n      .subscribe(document$)\n\n  /* Emit history state change */\n  merge(push$, pop$)\n    .pipe(\n      sample(document$)\n    )\n      .subscribe(({ url, offset }) => {\n        if (url.hash && !offset)\n          setLocationHash(url.hash)\n        else\n          setViewportOffset(offset || { y: 0 })\n      })\n\n  /* Replace meta tags and components */\n  document$\n    .pipe(\n      skip(1)\n    )\n      .subscribe(replacement => {\n        for (const selector of [\n\n          /* Meta tags */\n          \"title\",\n          \"link[rel=canonical]\",\n          \"meta[name=author]\",\n          \"meta[name=description]\",\n\n          /* Components */\n          \"[data-md-component=announce]\",\n          \"[data-md-component=container]\",\n          \"[data-md-component=header-topic]\",\n          \"[data-md-component=logo], .md-logo\", // compat\n          \"[data-md-component=skip]\"\n        ]) {\n          const source = getElement(selector)\n          const target = getElement(selector, replacement)\n          if (\n            typeof source !== \"undefined\" &&\n            typeof target !== \"undefined\"\n          ) {\n            replaceElement(source, target)\n          }\n        }\n      })\n\n  /* Re-evaluate scripts */\n  document$\n    .pipe(\n      skip(1),\n      map(() => getComponentElement(\"container\")),\n      switchMap(el => of(...getElements(\"script\", el))),\n      concatMap(el => {\n        const script = createElement(\"script\")\n        if (el.src) {\n          for (const name of el.getAttributeNames())\n            script.setAttribute(name, el.getAttribute(name)!)\n          replaceElement(el, script)\n\n          /* Complete when script is loaded */\n          return new Observable(observer => {\n            script.onload = () => observer.complete()\n          })\n\n        /* Complete immediately */\n        } else {\n          script.textContent = el.textContent\n          replaceElement(el, script)\n          return EMPTY\n        }\n      })\n    )\n      .subscribe()\n\n  /* Debounce update of viewport offset */\n  viewport$\n    .pipe(\n      skipUntil(push$),\n      debounceTime(250),\n      distinctUntilKeyChanged(\"offset\")\n    )\n      .subscribe(({ offset }) => {\n        history.replaceState(offset, \"\")\n      })\n\n  /* Set viewport offset from history */\n  merge(push$, pop$)\n    .pipe(\n      bufferCount(2, 1),\n      filter(([a, b]) => a.url.pathname === b.url.pathname),\n      map(([, state]) => state)\n    )\n      .subscribe(({ offset }) => {\n        setViewportOffset(offset || { y: 0 })\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n *    that the resulting document must contain all terms, converting the query\n *    to an `AND` query (as opposed to the default `OR` behavior). While users\n *    may expect terms enclosed in quotation marks to map to span queries, i.e.\n *    for which order is important, Lunr.js doesn't support them, so the best\n *    we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n *    query or preceded by white space, or are not followed by a non-whitespace\n *    character or are at the end of the query string. Furthermore, filter\n *    unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n  return query\n    .split(/\"([^\"]+)\"/g)                            /* => 1 */\n      .map((terms, index) => index & 1\n        ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n        : terms\n      )\n      .join(\"\")\n    .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n    .trim()                                         /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ObservableInput, Subject, from } from \"rxjs\"\nimport { map, share } from \"rxjs/operators\"\n\nimport { configuration, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex, SearchIndexPipeline } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType,\n  SearchSetupMessage,\n  isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler<SearchMessage>\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex(\n  { config, docs, index }: SearchIndex\n): SearchIndex {\n\n  /* Override default language with value from translation */\n  if (config.lang.length === 1 && config.lang[0] === \"en\")\n    config.lang = [\n      translation(\"search.config.lang\")\n    ]\n\n  /* Override default separator with value from translation */\n  if (config.separator === \"[\\\\s\\\\-]+\")\n    config.separator = translation(\"search.config.separator\")\n\n  /* Set pipeline from translation */\n  const pipeline = translation(\"search.config.pipeline\")\n    .split(/\\s*,\\s*/)\n    .filter(Boolean) as SearchIndexPipeline\n\n  /* Return search index after defaulting */\n  return { config, docs, index, pipeline }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n  url: string, index: ObservableInput<SearchIndex>\n): SearchWorker {\n  const config = configuration()\n  const worker = new Worker(url)\n\n  /* Create communication channels and resolve relative links */\n  const tx$ = new Subject<SearchMessage>()\n  const rx$ = watchWorker(worker, { tx$ })\n    .pipe(\n      map(message => {\n        if (isSearchResultMessage(message)) {\n          for (const result of message.data)\n            for (const document of result)\n              document.location = `${config.base}/${document.location}`\n        }\n        return message\n      }),\n      share()\n    )\n\n  /* Set up search index */\n  from(index)\n    .pipe(\n      map<SearchIndex, SearchSetupMessage>(data => ({\n        type: SearchMessageType.SETUP,\n        data: setupSearchIndex(data)\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Return search worker */\n  return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration } from \"~/_\"\nimport { getElementOrThrow, requestJSON } from \"~/browser\"\nimport { Version, renderVersionSelector } from \"~/templates\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n */\nexport function setupVersionSelector(): void {\n  const config = configuration()\n  requestJSON<Version[]>(new URL(\"versions.json\", config.base))\n    .subscribe(versions => {\n      const topic = getElementOrThrow(\".md-header__topic\")\n      topic.appendChild(renderVersionSelector(versions))\n    })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  combineLatest,\n  fromEvent,\n  merge\n} from \"rxjs\"\nimport {\n  delay,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  takeLast,\n  takeUntil,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetSearchQueryPlaceholder,\n  setSearchQueryPlaceholder\n} from \"~/actions\"\nimport {\n  setElementFocus,\n  setToggle,\n  watchElementFocus\n} from \"~/browser\"\nimport {\n  SearchMessageType,\n  SearchQueryMessage,\n  SearchWorker,\n  defaultTransform\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n  value: string                        /* Query value */\n  focus: boolean                       /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n  el: HTMLInputElement\n): Observable<SearchQuery> {\n  const fn = __search?.transform || defaultTransform\n\n  /* Intercept focus and input events */\n  const focus$ = watchElementFocus(el)\n  const value$ = merge(\n    fromEvent(el, \"keyup\"),\n    fromEvent(el, \"focus\").pipe(delay(1))\n  )\n    .pipe(\n      map(() => fn(el.value)),\n      distinctUntilChanged()\n    )\n\n  /* Combine into single observable */\n  return combineLatest([value$, focus$])\n    .pipe(\n      map(([value, focus]) => ({ value, focus }))\n    )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n  el: HTMLInputElement, { tx$ }: SearchWorker\n): Observable<Component<SearchQuery, HTMLInputElement>> {\n  const internal$ = new Subject<SearchQuery>()\n\n  /* Handle value changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"value\"),\n      map(({ value }): SearchQueryMessage => ({\n        type: SearchMessageType.QUERY,\n        data: value\n      }))\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Handle focus changes */\n  internal$\n    .pipe(\n      distinctUntilKeyChanged(\"focus\")\n    )\n      .subscribe(({ focus }) => {\n        if (focus) {\n          setToggle(\"search\", focus)\n          setSearchQueryPlaceholder(el, \"\")\n        } else {\n          resetSearchQueryPlaceholder(el)\n        }\n      })\n\n  /* Handle reset */\n  fromEvent(el.form!, \"reset\")\n    .pipe(\n      takeUntil(internal$.pipe(takeLast(1)))\n    )\n      .subscribe(() => setElementFocus(el))\n\n  /* Create and return component */\n  return watchSearchQuery(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  merge,\n  of\n} from \"rxjs\"\nimport {\n  bufferCount,\n  filter,\n  finalize,\n  map,\n  observeOn,\n  startWith,\n  switchMap,\n  tap,\n  withLatestFrom,\n  zipWith\n} from \"rxjs/operators\"\n\nimport {\n  addToSearchResultList,\n  resetSearchResultList,\n  resetSearchResultMeta,\n  setSearchResultMeta\n} from \"~/actions\"\nimport {\n  getElementOrThrow,\n  watchElementThreshold\n} from \"~/browser\"\nimport {\n  SearchResult as SearchResultData,\n  SearchWorker,\n  isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResult } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n  data: SearchResultData[]             /* Search result data */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  query$: Observable<SearchQuery>      /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n  el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable<Component<SearchResult>> {\n  const internal$ = new Subject<SearchResult>()\n  const boundary$ = watchElementThreshold(el.parentElement!)\n    .pipe(\n      filter(Boolean)\n    )\n\n  /* Update search result metadata */\n  const meta = getElementOrThrow(\":scope > :first-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(query$)\n    )\n      .subscribe(([{ data }, { value }]) => {\n        if (value)\n          setSearchResultMeta(meta, data.length)\n        else\n          resetSearchResultMeta(meta)\n      })\n\n  /* Update search result list */\n  const list = getElementOrThrow(\":scope > :last-child\", el)\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      tap(() => resetSearchResultList(list)),\n      switchMap(({ data }) => merge(\n        of(...data.slice(0, 10)),\n        of(...data.slice(10))\n          .pipe(\n            bufferCount(4),\n            zipWith(boundary$),\n            switchMap(([chunk]) => of(...chunk))\n          )\n      ))\n    )\n      .subscribe(result => {\n        addToSearchResultList(list, renderSearchResult(result))\n      })\n\n  /* Filter search result list */\n  const result$ = rx$\n    .pipe(\n      filter(isSearchResultMessage),\n      map(({ data }) => ({ data })),\n      startWith({ data: [] })\n    )\n\n  /* Create and return component */\n  return result$\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, ObservableInput, merge } from \"rxjs\"\nimport { filter, sample, take } from \"rxjs/operators\"\n\nimport { configuration } from \"~/_\"\nimport {\n  Keyboard,\n  getActiveElement,\n  getElements,\n  setElementFocus,\n  setElementSelection,\n  setToggle\n} from \"~/browser\"\nimport {\n  SearchIndex,\n  isSearchQueryMessage,\n  isSearchReadyMessage,\n  setupSearchWorker\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\nimport { SearchQuery, mountSearchQuery } from \"../query\"\nimport { SearchResult, mountSearchResult } from \"../result\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n  | SearchQuery\n  | SearchResult\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  index$: ObservableInput<SearchIndex> /* Search index observable */\n  keyboard$: Observable<Keyboard>      /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n  el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable<Component<Search>> {\n  const config = configuration()\n  const worker = setupSearchWorker(config.search, index$)\n\n  /* Retrieve nested components */\n  const query  = getComponentElement(\"search-query\", el)\n  const result = getComponentElement(\"search-result\", el)\n\n  /* Re-emit query when search is ready */\n  const { tx$, rx$ } = worker\n  tx$\n    .pipe(\n      filter(isSearchQueryMessage),\n      sample(rx$.pipe(filter(isSearchReadyMessage))),\n      take(1)\n    )\n      .subscribe(tx$.next.bind(tx$))\n\n  /* Set up search keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"search\")\n    )\n      .subscribe(key => {\n        const active = getActiveElement()\n        switch (key.type) {\n\n          /* Enter: prevent form submission */\n          case \"Enter\":\n            if (active === query)\n              key.claim()\n            break\n\n          /* Escape or Tab: close search */\n          case \"Escape\":\n          case \"Tab\":\n            setToggle(\"search\", false)\n            setElementFocus(query, false)\n            break\n\n          /* Vertical arrows: select previous or next search result */\n          case \"ArrowUp\":\n          case \"ArrowDown\":\n            if (typeof active === \"undefined\") {\n              setElementFocus(query)\n            } else {\n              const els = [query, ...getElements(\n                \":not(details) > [href], summary, details[open] [href]\",\n                result\n              )]\n              const i = Math.max(0, (\n                Math.max(0, els.indexOf(active)) + els.length + (\n                  key.type === \"ArrowUp\" ? -1 : +1\n                )\n              ) % els.length)\n              setElementFocus(els[i])\n            }\n\n            /* Prevent scrolling of page */\n            key.claim()\n            break\n\n          /* All other keys: hand to search query */\n          default:\n            if (query !== getActiveElement())\n              setElementFocus(query)\n        }\n      })\n\n  /* Set up global keyboard handlers */\n  keyboard$\n    .pipe(\n      filter(({ mode }) => mode === \"global\"),\n    )\n      .subscribe(key => {\n        switch (key.type) {\n\n          /* Open search and select query */\n          case \"f\":\n          case \"s\":\n          case \"/\":\n            setElementFocus(query)\n            setElementSelection(query)\n            key.claim()\n            break\n        }\n      })\n\n  /* Create and return component */\n  const query$ = mountSearchQuery(query, worker)\n  return merge(\n    query$,\n    mountSearchResult(result, worker, { query$ })\n  )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  distinctUntilChanged,\n  finalize,\n  map,\n  observeOn,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport {\n  resetSidebarHeight,\n  resetSidebarOffset,\n  setSidebarHeight,\n  setSidebarOffset\n} from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n  height: number                       /* Sidebar height */\n  locked: boolean                      /* User scrolled past header */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n  el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<Sidebar> {\n  const adjust =\n    el.parentElement!.offsetTop -\n    el.parentElement!.parentElement!.offsetTop\n\n  /* Compute the sidebar's available height and if it should be locked */\n  return combineLatest([main$, viewport$])\n    .pipe(\n      map(([{ offset, height }, { offset: { y } }]) => {\n        height = height\n          + Math.min(adjust, Math.max(0, y - offset))\n          - adjust\n        return {\n          height,\n          locked: y >= offset + adjust\n        }\n      }),\n      distinctUntilChanged((a, b) => (\n        a.height === b.height &&\n        a.locked === b.locked\n      ))\n    )\n}\n\n/**\n * Mount sidebar\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n  el: HTMLElement, { header$, ...options }: MountOptions\n): Observable<Component<Sidebar>> {\n  const internal$ = new Subject<Sidebar>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n      withLatestFrom(header$)\n    )\n      .subscribe({\n\n        /* Update height and offset */\n        next([{ height }, { height: offset }]) {\n          setSidebarHeight(el, height)\n          setSidebarOffset(el, offset)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetSidebarOffset(el)\n          resetSidebarHeight(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchSidebar(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport { Observable, zip } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * GitHub release (partial)\n */\ninterface Release {\n  tag_name: string                     /* Tag name */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n  user: string, repo?: string\n): Observable<SourceFacts> {\n  if (typeof repo !== \"undefined\") {\n    const url = `https://api.github.com/repos/${user}/${repo}`\n    return zip(\n\n      /* Fetch version */\n      requestJSON<Release>(`${url}/releases/latest`)\n        .pipe(\n          map(release => ({\n            version: release.tag_name\n          })),\n          defaultIfEmpty({})\n        ),\n\n      /* Fetch stars and forks */\n      requestJSON<Repo>(url)\n        .pipe(\n          map(info => ({\n            stars: info.stargazers_count,\n            forks: info.forks_count\n          })),\n          defaultIfEmpty({})\n        )\n    )\n      .pipe(\n        map(([release, info]) => ({ ...release, ...info }))\n      )\n\n  /* User or organization */\n  } else {\n    const url = `https://api.github.com/repos/${user}`\n    return requestJSON<User>(url)\n      .pipe(\n        map(info => ({\n          repositories: info.public_repos\n        })),\n        defaultIfEmpty({})\n      )\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport { Observable } from \"rxjs\"\nimport { defaultIfEmpty, map } from \"rxjs/operators\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n  base: string, project: string\n): Observable<SourceFacts> {\n  const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n  return requestJSON<ProjectSchema>(url)\n    .pipe(\n      map(({ star_count, forks_count }) => ({\n        stars: star_count,\n        forks: forks_count\n      })),\n      defaultIfEmpty({})\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts for repositories\n */\nexport interface RepositoryFacts {\n  stars?: number                       /* Number of stars */\n  forks?: number                       /* Number of forks */\n  version?: string                     /* Latest version */\n}\n\n/**\n * Repository facts for organizations\n */\nexport interface OrganizationFacts {\n  repositories?: number                /* Number of repositories */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts =\n  | RepositoryFacts\n  | OrganizationFacts\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n  url: string\n): Observable<SourceFacts> {\n  const [type] = url.match(/(git(?:hub|lab))/i) || []\n  switch (type.toLowerCase()) {\n\n    /* GitHub repository */\n    case \"github\":\n      const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n      return fetchSourceFactsFromGitHub(user, repo)\n\n    /* GitLab repository */\n    case \"gitlab\":\n      const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n      return fetchSourceFactsFromGitLab(base, slug)\n\n    /* Everything else */\n    default:\n      return NEVER\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { NEVER, Observable, Subject, defer, of } from \"rxjs\"\nimport {\n  catchError,\n  filter,\n  finalize,\n  map,\n  shareReplay,\n  tap\n} from \"rxjs/operators\"\n\nimport { setSourceFacts, setSourceState } from \"~/actions\"\nimport { renderSourceFacts } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport { SourceFacts, fetchSourceFacts } from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n  facts: SourceFacts                   /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable<Source>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n  el: HTMLAnchorElement\n): Observable<Source> {\n  return fetch$ ||= defer(() => {\n    const data = sessionStorage.getItem(__prefix(\"__source\"))\n    if (data) {\n      return of<SourceFacts>(JSON.parse(data))\n    } else {\n      const value$ = fetchSourceFacts(el.href)\n      value$.subscribe(value => {\n        try {\n          sessionStorage.setItem(__prefix(\"__source\"), JSON.stringify(value))\n        } catch (err) {\n          /* Uncritical, just swallow */\n        }\n      })\n\n      /* Return value */\n      return value$\n    }\n  })\n    .pipe(\n      catchError(() => NEVER),\n      filter(facts => Object.keys(facts).length > 0),\n      map(facts => ({ facts })),\n      shareReplay(1)\n    )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n  el: HTMLAnchorElement\n): Observable<Component<Source>> {\n  const internal$ = new Subject<Source>()\n  internal$.subscribe(({ facts }) => {\n    setSourceFacts(el, renderSourceFacts(facts))\n    setSourceState(el, \"done\")\n  })\n\n  /* Create and return component */\n  return watchSource(el)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, Subject, animationFrameScheduler } from \"rxjs\"\nimport {\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetTabsState, setTabsState } from \"~/actions\"\nimport {\n  Viewport,\n  watchElementSize,\n  watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n  hidden: boolean                      /* User scrolled past tabs */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n  el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable<Tabs> {\n  return watchElementSize(document.body)\n    .pipe(\n      switchMap(() => watchViewportAt(el, { header$, viewport$ })),\n      map(({ offset: { y } }) => {\n        return {\n          hidden: y >= 10\n        }\n      }),\n      distinctUntilKeyChanged(\"hidden\")\n    )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<Tabs>> {\n  const internal$ = new Subject<Tabs>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setTabsState(el, \"hidden\")\n          else\n            resetTabsState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetTabsState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchTabs(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  scan,\n  startWith,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport {\n  resetAnchorActive,\n  resetAnchorState,\n  setAnchorActive,\n  setAnchorState\n} from \"~/actions\"\nimport {\n  Viewport,\n  getElement,\n  getElements,\n  watchElementSize\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n  prev: HTMLAnchorElement[][]          /* Anchors (previous) */\n  next: HTMLAnchorElement[][]          /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  header$: Observable<Header>          /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param anchors - Anchor elements\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n  anchors: HTMLAnchorElement[], { viewport$, header$ }: WatchOptions\n): Observable<TableOfContents> {\n  const table = new Map<HTMLAnchorElement, HTMLElement>()\n  for (const anchor of anchors) {\n    const id = decodeURIComponent(anchor.hash.substring(1))\n    const target = getElement(`[id=\"${id}\"]`)\n    if (typeof target !== \"undefined\")\n      table.set(anchor, target)\n  }\n\n  /* Compute necessary adjustment for header */\n  const adjust$ = header$\n    .pipe(\n      map(header => 24 + header.height)\n    )\n\n  /* Compute partition of previous and next anchors */\n  const partition$ = watchElementSize(document.body)\n    .pipe(\n      distinctUntilKeyChanged(\"height\"),\n\n      /* Build index to map anchor paths to vertical offsets */\n      map(() => {\n        let path: HTMLAnchorElement[] = []\n        return [...table].reduce((index, [anchor, target]) => {\n          while (path.length) {\n            const last = table.get(path[path.length - 1])!\n            if (last.tagName >= target.tagName) {\n              path.pop()\n            } else {\n              break\n            }\n          }\n\n          /* If the current anchor is hidden, continue with its parent */\n          let offset = target.offsetTop\n          while (!offset && target.parentElement) {\n            target = target.parentElement\n            offset = target.offsetTop\n          }\n\n          /* Map reversed anchor path to vertical offset */\n          return index.set(\n            [...path = [...path, anchor]].reverse(),\n            offset\n          )\n        }, new Map<HTMLAnchorElement[], number>())\n      }),\n\n      /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */\n      map(index => new Map([...index].sort(([, a], [, b]) => a - b))),\n\n      /* Re-compute partition when viewport offset changes */\n      switchMap(index => combineLatest([adjust$, viewport$])\n        .pipe(\n          scan(([prev, next], [adjust, { offset: { y } }]) => {\n\n            /* Look forward */\n            while (next.length) {\n              const [, offset] = next[0]\n              if (offset - adjust < y) {\n                prev = [...prev, next.shift()!]\n              } else {\n                break\n              }\n            }\n\n            /* Look backward */\n            while (prev.length) {\n              const [, offset] = prev[prev.length - 1]\n              if (offset - adjust >= y) {\n                next = [prev.pop()!, ...next]\n              } else {\n                break\n              }\n            }\n\n            /* Return partition */\n            return [prev, next]\n          }, [[], [...index]]),\n          distinctUntilChanged((a, b) => (\n            a[0] === b[0] &&\n            a[1] === b[1]\n          ))\n        )\n      )\n    )\n\n  /* Compute and return anchor list migrations */\n  return partition$\n    .pipe(\n      map(([prev, next]) => ({\n        prev: prev.map(([path]) => path),\n        next: next.map(([path]) => path)\n      })),\n\n      /* Extract anchor list migrations */\n      startWith({ prev: [], next: [] }),\n      bufferCount(2, 1),\n      map(([a, b]) => {\n\n        /* Moving down */\n        if (a.prev.length < b.prev.length) {\n          return {\n            prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n            next: []\n          }\n\n        /* Moving up */\n        } else {\n          return {\n            prev: b.prev.slice(-1),\n            next: b.next.slice(0, b.next.length - a.next.length)\n          }\n        }\n      })\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Anchor list element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<TableOfContents>> {\n  const internal$ = new Subject<TableOfContents>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler),\n    )\n      .subscribe(({ prev, next }) => {\n\n        /* Look forward */\n        for (const [anchor] of next) {\n          resetAnchorActive(anchor)\n          resetAnchorState(anchor)\n        }\n\n        /* Look backward */\n        for (const [index, [anchor]] of prev.entries()) {\n          setAnchorActive(anchor, index === prev.length - 1)\n          setAnchorState(anchor, \"blur\")\n        }\n      })\n\n  /* Create and return component */\n  const anchors = getElements<HTMLAnchorElement>(\"[href^=\\\\#]\", el)\n  return watchTableOfContents(anchors, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  combineLatest\n} from \"rxjs\"\nimport {\n  bufferCount,\n  distinctUntilChanged,\n  distinctUntilKeyChanged,\n  finalize,\n  map,\n  observeOn,\n  tap\n} from \"rxjs/operators\"\n\nimport { resetBackToTopState, setBackToTopState } from \"~/actions\"\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Back-to-top button\n */\nexport interface BackToTop {\n  hidden: boolean                      /* User scrolled up */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  main$: Observable<Main>              /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch back-to-top\n *\n * @param _el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top observable\n */\nexport function watchBackToTop(\n  _el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable<BackToTop> {\n\n  /* Compute direction */\n  const direction$ = viewport$\n    .pipe(\n      map(({ offset: { y } }) => y),\n      bufferCount(2, 1),\n      map(([a, b]) => a > b),\n      distinctUntilChanged()\n    )\n\n  /* Compute whether button should be hidden */\n  const hidden$ = main$\n    .pipe(\n      distinctUntilKeyChanged(\"active\")\n    )\n\n  /* Compute threshold for hiding */\n  return combineLatest([hidden$, direction$])\n    .pipe(\n      map(([{ active }, direction]) => ({\n        hidden: !(active && direction)\n      })),\n      distinctUntilChanged((a, b) => (\n        a.hidden === b.hidden\n      ))\n    )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount back-to-top\n *\n * @param el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top component observable\n */\nexport function mountBackToTop(\n  el: HTMLElement, options: MountOptions\n): Observable<Component<BackToTop>> {\n  const internal$ = new Subject<BackToTop>()\n  internal$\n    .pipe(\n      observeOn(animationFrameScheduler)\n    )\n      .subscribe({\n\n        /* Update state */\n        next({ hidden }) {\n          if (hidden)\n            setBackToTopState(el, \"hidden\")\n          else\n            resetBackToTopState(el)\n        },\n\n        /* Reset on complete */\n        complete() {\n          resetBackToTopState(el)\n        }\n      })\n\n  /* Create and return component */\n  return watchBackToTop(el, options)\n    .pipe(\n      tap(internal$),\n      finalize(() => internal$.complete()),\n      map(state => ({ ref: el, ...state }))\n    )\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  mapTo,\n  mergeMap,\n  switchMap,\n  takeWhile,\n  tap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n  { document$, tablet$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements<HTMLInputElement>(\n        \"[data-md-state=indeterminate]\"\n      ))),\n      tap(el => {\n        el.indeterminate = true\n        el.checked = false\n      }),\n      mergeMap(el => fromEvent(el, \"change\")\n        .pipe(\n          takeWhile(() => el.hasAttribute(\"data-md-state\")),\n          mapTo(el)\n        )\n      ),\n      withLatestFrom(tablet$)\n    )\n      .subscribe(([el, tablet]) => {\n        el.removeAttribute(\"data-md-state\")\n        if (tablet)\n          el.checked = false\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, fromEvent, of } from \"rxjs\"\nimport {\n  filter,\n  mapTo,\n  mergeMap,\n  switchMap,\n  tap\n} from \"rxjs/operators\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  document$: Observable<Document>      /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n  return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n  { document$ }: PatchOptions\n): void {\n  document$\n    .pipe(\n      switchMap(() => of(...getElements(\"[data-md-scrollfix]\"))),\n      tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n      filter(isAppleDevice),\n      mergeMap(el => fromEvent(el, \"touchstart\")\n        .pipe(\n          mapTo(el)\n        )\n      )\n    )\n      .subscribe(el => {\n        const top = el.scrollTop\n\n        /* We're at the top of the container */\n        if (top === 0) {\n          el.scrollTop = 1\n\n        /* We're at the bottom of the container */\n        } else if (top + el.offsetHeight === el.scrollHeight) {\n          el.scrollTop = top - 1\n        }\n      })\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  Observable,\n  animationFrameScheduler,\n  combineLatest,\n  of\n} from \"rxjs\"\nimport {\n  delay,\n  map,\n  observeOn,\n  switchMap,\n  withLatestFrom\n} from \"rxjs/operators\"\n\nimport { resetScrollLock, setScrollLock } from \"~/actions\"\nimport { Viewport, watchToggle } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n  viewport$: Observable<Viewport>      /* Viewport observable */\n  tablet$: Observable<boolean>         /* Tablet breakpoint observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n  { viewport$, tablet$ }: PatchOptions\n): void {\n  combineLatest([watchToggle(\"search\"), tablet$])\n    .pipe(\n      map(([active, tablet]) => active && !tablet),\n      switchMap(active => of(active)\n        .pipe(\n          delay(active ? 400 : 100),\n          observeOn(animationFrameScheduler)\n        )\n      ),\n      withLatestFrom(viewport$)\n    )\n      .subscribe(([active, { offset: { y }}]) => {\n        if (active)\n          setScrollLock(document.body, y)\n        else\n          resetScrollLock(document.body)\n      })\n}\n"],
+  "mappings": "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",
+  "names": []
+}
diff --git a/latest/assets/javascripts/workers/search.fb4a9340.min.js b/latest/assets/javascripts/workers/search.fb4a9340.min.js
deleted file mode 100644 (file)
index bf8dbff..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-(()=>{var le=Object.create,U=Object.defineProperty,he=Object.getPrototypeOf,de=Object.prototype.hasOwnProperty,fe=Object.getOwnPropertyNames,pe=Object.getOwnPropertyDescriptor;var ge=t=>U(t,"__esModule",{value:!0});var q=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var ye=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of fe(e))!de.call(t,n)&&n!=="default"&&U(t,n,{get:()=>e[n],enumerable:!(r=pe(e,n))||r.enumerable});return t},Y=t=>t&&t.__esModule?t:ye(ge(U(t!=null?le(he(t)):{},"default",{value:t,enumerable:!0})),t);var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var X=q((G,J)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i<n.length;i++){var s=n[i],o=e[s];if(Array.isArray(o)){r[s]=o.slice();continue}if(typeof o=="string"||typeof o=="number"||typeof o=="boolean"){r[s]=o;continue}throw new TypeError("clone is not deep and does not support nested objects")}return r},t.FieldRef=function(e,r,n){this.docRef=e,this.fieldName=r,this._stringValue=n},t.FieldRef.joiner="/",t.FieldRef.fromString=function(e){var r=e.indexOf(t.FieldRef.joiner);if(r===-1)throw"malformed field ref string";var n=e.slice(0,r),i=e.slice(r+1);return new t.FieldRef(i,n,e)},t.FieldRef.prototype.toString=function(){return this._stringValue==null&&(this._stringValue=this.fieldName+t.FieldRef.joiner+this.docRef),this._stringValue};t.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var r=0;r<this.length;r++)this.elements[e[r]]=!0}else this.length=0},t.Set.complete={intersect:function(e){return e},union:function(){return this},contains:function(){return!0}},t.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},t.Set.prototype.contains=function(e){return!!this.elements[e]},t.Set.prototype.intersect=function(e){var r,n,i,s=[];if(e===t.Set.complete)return this;if(e===t.Set.empty)return e;this.length<e.length?(r=this,n=e):(r=e,n=this),i=Object.keys(r.elements);for(var o=0;o<i.length;o++){var a=i[o];a in n.elements&&s.push(a)}return new t.Set(s)},t.Set.prototype.union=function(e){return e===t.Set.complete?t.Set.complete:e===t.Set.empty?this:new t.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},t.idf=function(e,r){var n=0;for(var i in e)i!="_index"&&(n+=Object.keys(e[i]).length);var s=(r-n+.5)/(n+.5);return Math.log(1+Math.abs(s))},t.Token=function(e,r){this.str=e||"",this.metadata=r||{}},t.Token.prototype.toString=function(){return this.str},t.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},t.Token.prototype.clone=function(e){return e=e||function(r){return r},new t.Token(e(this.str,this.metadata),this.metadata)};t.tokenizer=function(e,r){if(e==null||e==null)return[];if(Array.isArray(e))return e.map(function(y){return new t.Token(t.utils.asString(y).toLowerCase(),t.utils.clone(r))});for(var n=e.toString().toLowerCase(),i=n.length,s=[],o=0,a=0;o<=i;o++){var u=n.charAt(o),c=o-a;if(u.match(t.tokenizer.separator)||o==i){if(c>0){var d=t.utils.clone(r)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index.
-`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n<r;n++){for(var i=this._stack[n],s=[],o=0;o<e.length;o++){var a=i(e[o],o,e);if(!(a==null||a===""))if(Array.isArray(a))for(var u=0;u<a.length;u++)s.push(a[u]);else s.push(a)}e=s}return e},t.Pipeline.prototype.runString=function(e,r){var n=new t.Token(e,r);return this.run([n]).map(function(i){return i.toString()})},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})};t.Vector=function(e){this._magnitude=0,this.elements=e||[]},t.Vector.prototype.positionForIndex=function(e){if(this.elements.length==0)return 0;for(var r=0,n=this.elements.length/2,i=n-r,s=Math.floor(i/2),o=this.elements[s*2];i>1&&(o<e&&(r=s),o>e&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(o<e)return(s+1)*2},t.Vector.prototype.insert=function(e,r){this.upsert(e,r,function(){throw"duplicate index"})},t.Vector.prototype.upsert=function(e,r,n){this._magnitude=0;var i=this.positionForIndex(e);this.elements[i]==e?this.elements[i+1]=n(this.elements[i+1],r):this.elements.splice(i,0,e,r)},t.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,r=this.elements.length,n=1;n<r;n+=2){var i=this.elements[n];e+=i*i}return this._magnitude=Math.sqrt(e)},t.Vector.prototype.dot=function(e){for(var r=0,n=this.elements,i=e.elements,s=n.length,o=i.length,a=0,u=0,c=0,d=0;c<s&&d<o;)a=n[c],u=i[d],a<u?c+=2:a>u?d+=2:a==u&&(r+=n[c+1]*i[d+1],c+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r<this.elements.length;r+=2,n++)e[n]=this.elements[r];return e},t.Vector.prototype.toJSON=function(){return this.elements};t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},r={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",s=n+"[^aeiouy]*",o=i+"[aeiou]*",a="^("+s+")?"+o+s,u="^("+s+")?"+o+s+"("+o+")?$",c="^("+s+")?"+o+s+o+s,d="^("+s+")?"+i,y=new RegExp(a),p=new RegExp(c),b=new RegExp(u),m=new RegExp(d),Q=/^(.+?)(ss|i)es$/,f=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,L=/^(.+?)(ed|ing)$/,w=/.$/,k=/(at|bl|iz)$/,O=new RegExp("([^aeiouylsz])\\1$"),j=new RegExp("^"+s+i+"[^aeiouwxy]$"),C=/^(.+?[^aeiou])y$/,A=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,V=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,$=/^(.+?)(s|t)(ion)$/,P=/^(.+?)e$/,N=/ll$/,B=new RegExp("^"+s+i+"[^aeiouwxy]$"),M=function(l){var v,I,E,h,x,T,F;if(l.length<3)return l;if(E=l.substr(0,1),E=="y"&&(l=E.toUpperCase()+l.substr(1)),h=Q,x=f,h.test(l)?l=l.replace(h,"$1$2"):x.test(l)&&(l=l.replace(x,"$1$2")),h=g,x=L,h.test(l)){var S=h.exec(l);h=y,h.test(S[1])&&(h=w,l=l.replace(h,""))}else if(x.test(l)){var S=x.exec(l);v=S[1],x=m,x.test(v)&&(l=v,x=k,T=O,F=j,x.test(l)?l=l+"e":T.test(l)?(h=w,l=l.replace(h,"")):F.test(l)&&(l=l+"e"))}if(h=C,h.test(l)){var S=h.exec(l);v=S[1],l=v+"i"}if(h=A,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+e[I])}if(h=V,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+r[I])}if(h=D,x=$,h.test(l)){var S=h.exec(l);v=S[1],h=p,h.test(v)&&(l=v)}else if(x.test(l)){var S=x.exec(l);v=S[1]+S[2],x=p,x.test(v)&&(l=v)}if(h=P,h.test(l)){var S=h.exec(l);v=S[1],h=p,x=b,T=B,(h.test(v)||x.test(v)&&!T.test(v))&&(l=v)}return h=N,x=p,h.test(l)&&x.test(l)&&(h=w,l=l.replace(h,"")),E=="y"&&(l=E.toLowerCase()+l.substr(1)),l};return function(_){return _.update(M)}}(),t.Pipeline.registerFunction(t.stemmer,"stemmer");t.generateStopWordFilter=function(e){var r=e.reduce(function(n,i){return n[i]=i,n},{});return function(n){if(n&&r[n.toString()]!==n.toString())return n}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter");t.trimmer=function(e){return e.update(function(r){return r.replace(/^\W+/,"").replace(/\W+$/,"")})},t.Pipeline.registerFunction(t.trimmer,"trimmer");t.TokenSet=function(){this.final=!1,this.edges={},this.id=t.TokenSet._nextId,t.TokenSet._nextId+=1},t.TokenSet._nextId=1,t.TokenSet.fromArray=function(e){for(var r=new t.TokenSet.Builder,n=0,i=e.length;n<i;n++)r.insert(e[n]);return r.finish(),r.root},t.TokenSet.fromClause=function(e){return"editDistance"in e?t.TokenSet.fromFuzzyString(e.term,e.editDistance):t.TokenSet.fromString(e.term)},t.TokenSet.fromFuzzyString=function(e,r){for(var n=new t.TokenSet,i=[{node:n,editsRemaining:r,str:e}];i.length;){var s=i.pop();if(s.str.length>0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),y=s.str.charAt(1),p;y in s.node.edges?p=s.node.edges[y]:(p=new t.TokenSet,s.node.edges[y]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i<s;i++){var o=e[i],a=i==s-1;if(o=="*")r.edges[o]=r,r.final=a;else{var u=new t.TokenSet;u.final=a,r.edges[o]=u,r=u}}return n},t.TokenSet.prototype.toArray=function(){for(var e=[],r=[{prefix:"",node:this}];r.length;){var n=r.pop(),i=Object.keys(n.node.edges),s=i.length;n.node.final&&(n.prefix.charAt(0),e.push(n.prefix));for(var o=0;o<s;o++){var a=i[o];r.push({prefix:n.prefix.concat(a),node:n.node.edges[a]})}}return e},t.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",r=Object.keys(this.edges).sort(),n=r.length,i=0;i<n;i++){var s=r[i],o=this.edges[s];e=e+s+o.id}return e},t.TokenSet.prototype.intersect=function(e){for(var r=new t.TokenSet,n=void 0,i=[{qNode:e,output:r,node:this}];i.length;){n=i.pop();for(var s=Object.keys(n.qNode.edges),o=s.length,a=Object.keys(n.node.edges),u=a.length,c=0;c<o;c++)for(var d=s[c],y=0;y<u;y++){var p=a[y];if(p==d||d=="*"){var b=n.node.edges[p],m=n.qNode.edges[d],Q=b.final&&m.final,f=void 0;p in n.output.edges?(f=n.output.edges[p],f.final=f.final||Q):(f=new t.TokenSet,f.final=Q,n.output.edges[p]=f),i.push({qNode:m,output:f,node:b})}}}return r},t.TokenSet.Builder=function(){this.previousWord="",this.root=new t.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},t.TokenSet.Builder.prototype.insert=function(e){var r,n=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var i=0;i<e.length&&i<this.previousWord.length&&e[i]==this.previousWord[i];i++)n++;this.minimize(n),this.uncheckedNodes.length==0?r=this.root:r=this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(var i=n;i<e.length;i++){var s=new t.TokenSet,o=e[i];r.edges[o]=s,this.uncheckedNodes.push({parent:r,char:o,child:s}),r=s}r.final=!0,this.previousWord=e},t.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},t.TokenSet.Builder.prototype.minimize=function(e){for(var r=this.uncheckedNodes.length-1;r>=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u<this.fields.length;u++)i[this.fields[u]]=new t.Vector;e.call(r,r);for(var u=0;u<r.clauses.length;u++){var c=r.clauses[u],d=null,y=t.Set.empty;c.usePipeline?d=this.pipeline.runString(c.term,{fields:c.fields}):d=[c.term];for(var p=0;p<d.length;p++){var b=d[p];c.term=b;var m=t.TokenSet.fromClause(c),Q=this.tokenSet.intersect(m).toArray();if(Q.length===0&&c.presence===t.Query.presence.REQUIRED){for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=t.Set.empty}break}for(var L=0;L<Q.length;L++)for(var w=Q[L],k=this.invertedIndex[w],O=k._index,f=0;f<c.fields.length;f++){var g=c.fields[f],j=k[g],C=Object.keys(j),A=w+"/"+g,V=new t.Set(C);if(c.presence==t.Query.presence.REQUIRED&&(y=y.union(V),o[g]===void 0&&(o[g]=t.Set.complete)),c.presence==t.Query.presence.PROHIBITED){a[g]===void 0&&(a[g]=t.Set.empty),a[g]=a[g].union(V);continue}if(i[g].upsert(O,c.boost,function(ue,ce){return ue+ce}),!s[A]){for(var D=0;D<C.length;D++){var $=C[D],P=new t.FieldRef($,g),N=j[$],B;(B=n[P])===void 0?n[P]=new t.MatchData(w,g,N):B.add(w,g,N)}s[A]=!0}}}if(c.presence===t.Query.presence.REQUIRED)for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=o[g].intersect(y)}}for(var M=t.Set.complete,_=t.Set.empty,u=0;u<this.fields.length;u++){var g=this.fields[u];o[g]&&(M=M.intersect(o[g])),a[g]&&(_=_.union(a[g]))}var l=Object.keys(n),v=[],I=Object.create(null);if(r.isNegated()){l=Object.keys(this.fieldVectors);for(var u=0;u<l.length;u++){var P=l[u],E=t.FieldRef.fromString(P);n[P]=new t.MatchData}}for(var u=0;u<l.length;u++){var E=t.FieldRef.fromString(l[u]),h=E.docRef;if(!!M.contains(h)&&!_.contains(h)){var x=this.fieldVectors[E],T=i[E.fieldName].similarity(x),F;if((F=I[h])!==void 0)F.score+=T,F.matchData.combine(n[E]);else{var S={ref:h,score:T,matchData:n[E]};I[h]=S,v.push(S)}}}return v.sort(function(oe,ae){return ae.score-oe.score})},t.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map(function(n){return[n,this.invertedIndex[n]]},this),r=Object.keys(this.fieldVectors).map(function(n){return[n,this.fieldVectors[n].toJSON()]},this);return{version:t.version,fields:this.fields,fieldVectors:r,invertedIndex:e,pipeline:this.pipeline.toJSON()}},t.Index.load=function(e){var r={},n={},i=e.fieldVectors,s=Object.create(null),o=e.invertedIndex,a=new t.TokenSet.Builder,u=t.Pipeline.load(e.pipeline);e.version!=t.version&&t.utils.warn("Version mismatch when loading serialised index. Current version of lunr '"+t.version+"' does not match serialized index '"+e.version+"'");for(var c=0;c<i.length;c++){var d=i[c],y=d[0],p=d[1];n[y]=new t.Vector(p)}for(var c=0;c<o.length;c++){var d=o[c],b=d[0],m=d[1];a.insert(b),s[b]=m}return a.finish(),r.fields=e.fields,r.fieldVectors=n,r.invertedIndex=s,r.tokenSet=a.root,r.pipeline=u,new t.Index(r)};t.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=t.tokenizer,this.pipeline=new t.Pipeline,this.searchPipeline=new t.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},t.Builder.prototype.ref=function(e){this._ref=e},t.Builder.prototype.field=function(e,r){if(/\//.test(e))throw new RangeError("Field '"+e+"' contains illegal character '/'");this._fields[e]=r||{}},t.Builder.prototype.b=function(e){e<0?this._b=0:e>1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s<i.length;s++){var o=i[s],a=this._fields[o].extractor,u=a?a(e):e[o],c=this.tokenizer(u,{fields:[o]}),d=this.pipeline.run(c),y=new t.FieldRef(n,o),p=Object.create(null);this.fieldTermFrequencies[y]=p,this.fieldLengths[y]=0,this.fieldLengths[y]+=d.length;for(var b=0;b<d.length;b++){var m=d[b];if(p[m]==null&&(p[m]=0),p[m]+=1,this.invertedIndex[m]==null){var Q=Object.create(null);Q._index=this.termIndex,this.termIndex+=1;for(var f=0;f<i.length;f++)Q[i[f]]=Object.create(null);this.invertedIndex[m]=Q}this.invertedIndex[m][o][n]==null&&(this.invertedIndex[m][o][n]=Object.create(null));for(var g=0;g<this.metadataWhitelist.length;g++){var L=this.metadataWhitelist[g],w=m.metadata[L];this.invertedIndex[m][o][n][L]==null&&(this.invertedIndex[m][o][n][L]=[]),this.invertedIndex[m][o][n][L].push(w)}}}},t.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),r=e.length,n={},i={},s=0;s<r;s++){var o=t.FieldRef.fromString(e[s]),a=o.fieldName;i[a]||(i[a]=0),i[a]+=1,n[a]||(n[a]=0),n[a]+=this.fieldLengths[o]}for(var u=Object.keys(this._fields),s=0;s<u.length;s++){var c=u[s];n[c]=n[c]/i[c]}this.averageFieldLength=n},t.Builder.prototype.createFieldVectors=function(){for(var e={},r=Object.keys(this.fieldTermFrequencies),n=r.length,i=Object.create(null),s=0;s<n;s++){for(var o=t.FieldRef.fromString(r[s]),a=o.fieldName,u=this.fieldLengths[o],c=new t.Vector,d=this.fieldTermFrequencies[o],y=Object.keys(d),p=y.length,b=this._fields[a].boost||1,m=this._documents[o.docRef].boost||1,Q=0;Q<p;Q++){var f=y[Q],g=d[f],L=this.invertedIndex[f]._index,w,k,O;i[f]===void 0?(w=t.idf(this.invertedIndex[f],this.documentCount),i[f]=w):w=i[f],k=w*((this._k1+1)*g)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[a]))+g),k*=b,k*=m,O=Math.round(k*1e3)/1e3,c.insert(L,O)}e[o]=c}this.fieldVectors=e},t.Builder.prototype.createTokenSet=function(){this.tokenSet=t.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},t.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new t.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},t.Builder.prototype.use=function(e){var r=Array.prototype.slice.call(arguments,1);r.unshift(this),e.apply(this,r)},t.MatchData=function(e,r,n){for(var i=Object.create(null),s=Object.keys(n||{}),o=0;o<s.length;o++){var a=s[o];i[a]=n[a].slice()}this.metadata=Object.create(null),e!==void 0&&(this.metadata[e]=Object.create(null),this.metadata[e][r]=i)},t.MatchData.prototype.combine=function(e){for(var r=Object.keys(e.metadata),n=0;n<r.length;n++){var i=r[n],s=Object.keys(e.metadata[i]);this.metadata[i]==null&&(this.metadata[i]=Object.create(null));for(var o=0;o<s.length;o++){var a=s[o],u=Object.keys(e.metadata[i][a]);this.metadata[i][a]==null&&(this.metadata[i][a]=Object.create(null));for(var c=0;c<u.length;c++){var d=u[c];this.metadata[i][a][d]==null?this.metadata[i][a][d]=e.metadata[i][a][d]:this.metadata[i][a][d]=this.metadata[i][a][d].concat(e.metadata[i][a][d])}}}},t.MatchData.prototype.add=function(e,r,n){if(!(e in this.metadata)){this.metadata[e]=Object.create(null),this.metadata[e][r]=n;return}if(!(r in this.metadata[e])){this.metadata[e][r]=n;return}for(var i=Object.keys(n),s=0;s<i.length;s++){var o=i[s];o in this.metadata[e][r]?this.metadata[e][r][o]=this.metadata[e][r][o].concat(n[o]):this.metadata[e][r][o]=n[o]}},t.Query=function(e){this.clauses=[],this.allFields=e},t.Query.wildcard=new String("*"),t.Query.wildcard.NONE=0,t.Query.wildcard.LEADING=1,t.Query.wildcard.TRAILING=2,t.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},t.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=t.Query.wildcard.NONE),e.wildcard&t.Query.wildcard.LEADING&&e.term.charAt(0)!=t.Query.wildcard&&(e.term="*"+e.term),e.wildcard&t.Query.wildcard.TRAILING&&e.term.slice(-1)!=t.Query.wildcard&&(e.term=""+e.term+"*"),"presence"in e||(e.presence=t.Query.presence.OPTIONAL),this.clauses.push(e),this},t.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=t.Query.presence.PROHIBITED)return!1;return!0},t.Query.prototype.term=function(e,r){if(Array.isArray(e))return e.forEach(function(i){this.term(i,t.utils.clone(r))},this),this;var n=r||{};return n.term=e.toString(),this.clause(n),this},t.QueryParseError=function(e,r,n){this.name="QueryParseError",this.message=e,this.start=r,this.end=n},t.QueryParseError.prototype=new Error,t.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},t.QueryLexer.prototype.run=function(){for(var e=t.QueryLexer.lexText;e;)e=e(this)},t.QueryLexer.prototype.sliceString=function(){for(var e=[],r=this.start,n=this.pos,i=0;i<this.escapeCharPositions.length;i++)n=this.escapeCharPositions[i],e.push(this.str.slice(r,n)),r=n+1;return e.push(this.str.slice(r,this.pos)),this.escapeCharPositions.length=0,e.join("")},t.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},t.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},t.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos<this.length},t.QueryLexer.EOS="EOS",t.QueryLexer.FIELD="FIELD",t.QueryLexer.TERM="TERM",t.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",t.QueryLexer.BOOST="BOOST",t.QueryLexer.PRESENCE="PRESENCE",t.QueryLexer.lexField=function(e){return e.backup(),e.emit(t.QueryLexer.FIELD),e.ignore(),t.QueryLexer.lexText},t.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof G=="object"?J.exports=r():e.lunr=r()}(this,function(){return t})})()});var K=q((we,Z)=>{"use strict";var me=/["'&<>]/;Z.exports=ve;function ve(t){var e=""+t,r=me.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s<e.length;s++){switch(e.charCodeAt(s)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}o!==s&&(i+=e.substring(o,s)),o=s+1,i+=n}return o!==s?i+e.substring(o,s):i}});var se=Y(X());var ee=Y(K());function te(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=ee.default(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let c=e.get(i);r.has(c)?e.set(o,{location:o,title:a,text:u,parent:c}):(c.title=n.title,c.text=u,r.add(c))}else e.set(o,{location:o,title:a,text:u})}return e}function re(t){let e=new RegExp(t.separator,"img"),r=(n,i,s)=>`${i}<mark data-md-highlight>${s}</mark>`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${t.separator})(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(e,"|")})`,"img");return s=>s.replace(i,r).replace(/<\/mark>(\s+)<mark[^>]*>/img,"$1")}}function ne(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ie(t,e){let r=new Set(t),n={};for(let i=0;i<e.length;i++)for(let s of r)e[i].startsWith(s.term)&&(n[s.term]=!0,r.delete(s));for(let i of r)n[i.term]=!1;return n}function xe(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var W=class{constructor({config:e,docs:r,pipeline:n,index:i}){this.documents=te(r),this.highlight=re(e),lunr.tokenizer.separator=new RegExp(e.separator),typeof i=="undefined"?this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let s=xe(["trimmer","stopWordFilter","stemmer"],n);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.field("title",{boost:1e3}),this.field("text"),this.ref("location");for(let o of r)this.add(o)}):this.index=lunr.Index.load(i)}search(e){if(e)try{let r=this.highlight(e),n=ne(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED);return[...this.index.search(`${e}*`).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.documents.get(o);if(typeof c!="undefined"){let{location:d,title:y,text:p,parent:b}=c,m=ie(n,Object.keys(u.metadata)),Q=+!b+ +Object.values(m).every(f=>f);s.push({location:d,title:r(y),text:r(p),score:a*(1+Q),terms:m})}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.documents.get(o.location);if(typeof a!="undefined"){let u="parent"in a?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map).values()]}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return[]}};var R;(function(t){t[t.SETUP=0]="SETUP",t[t.READY=1]="READY",t[t.QUERY=2]="QUERY",t[t.RESULT=3]="RESULT"})(R||(R={}));var H;function Se(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang)n==="ja"&&r.push(`${e}/tinyseg.js`),n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`);t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Qe(t){return z(this,null,function*(){switch(t.type){case R.SETUP:return yield Se(t.data.config),H=new W(t.data),{type:R.READY};case R.QUERY:return{type:R.RESULT,data:H?H.search(t.data):[]};default:throw new TypeError("Invalid message type")}})}self.lunr=se.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Qe(t.data))}));})();
-/*!
- * escape-html
- * Copyright(c) 2012-2013 TJ Holowaychuk
- * Copyright(c) 2015 Andreas Lubbe
- * Copyright(c) 2015 Tiancheng "Timothy" Gu
- * MIT Licensed
- */
-/*!
- * lunr.Builder
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Index
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Pipeline
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Set
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.TokenSet
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.Vector
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.stemmer
- * Copyright (C) 2020 Oliver Nightingale
- * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
- */
-/*!
- * lunr.stopWordFilter
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.tokenizer
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.trimmer
- * Copyright (C) 2020 Oliver Nightingale
- */
-/*!
- * lunr.utils
- * Copyright (C) 2020 Oliver Nightingale
- */
-/**
- * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
- * Copyright (C) 2020 Oliver Nightingale
- * @license MIT
- */
-//# sourceMappingURL=search.fb4a9340.min.js.map
-
diff --git a/latest/assets/javascripts/workers/search.fb4a9340.min.js.map b/latest/assets/javascripts/workers/search.fb4a9340.min.js.map
deleted file mode 100644 (file)
index f7ea053..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "version": 3,
-  "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts"],
-  "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n *   this.field('title')\n *   this.field('body')\n *   this.ref('id')\n *\n *   documents.forEach(function (doc) {\n *     this.add(doc)\n *   }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n  var builder = new lunr.Builder\n\n  builder.pipeline.add(\n    lunr.trimmer,\n    lunr.stopWordFilter,\n    lunr.stemmer\n  )\n\n  builder.searchPipeline.add(\n    lunr.stemmer\n  )\n\n  config.call(builder, builder)\n  return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n  /* eslint-disable no-console */\n  return function (message) {\n    if (global.console && console.warn) {\n      console.warn(message)\n    }\n  }\n  /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n  if (obj === void 0 || obj === null) {\n    return \"\"\n  } else {\n    return obj.toString()\n  }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n  if (obj === null || obj === undefined) {\n    return obj\n  }\n\n  var clone = Object.create(null),\n      keys = Object.keys(obj)\n\n  for (var i = 0; i < keys.length; i++) {\n    var key = keys[i],\n        val = obj[key]\n\n    if (Array.isArray(val)) {\n      clone[key] = val.slice()\n      continue\n    }\n\n    if (typeof val === 'string' ||\n        typeof val === 'number' ||\n        typeof val === 'boolean') {\n      clone[key] = val\n      continue\n    }\n\n    throw new TypeError(\"clone is not deep and does not support nested objects\")\n  }\n\n  return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n  this.docRef = docRef\n  this.fieldName = fieldName\n  this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n  var n = s.indexOf(lunr.FieldRef.joiner)\n\n  if (n === -1) {\n    throw \"malformed field ref string\"\n  }\n\n  var fieldRef = s.slice(0, n),\n      docRef = s.slice(n + 1)\n\n  return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n  if (this._stringValue == undefined) {\n    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n  }\n\n  return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n  this.elements = Object.create(null)\n\n  if (elements) {\n    this.length = elements.length\n\n    for (var i = 0; i < this.length; i++) {\n      this.elements[elements[i]] = true\n    }\n  } else {\n    this.length = 0\n  }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n  intersect: function (other) {\n    return other\n  },\n\n  union: function () {\n    return this\n  },\n\n  contains: function () {\n    return true\n  }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n  intersect: function () {\n    return this\n  },\n\n  union: function (other) {\n    return other\n  },\n\n  contains: function () {\n    return false\n  }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n  return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n  var a, b, elements, intersection = []\n\n  if (other === lunr.Set.complete) {\n    return this\n  }\n\n  if (other === lunr.Set.empty) {\n    return other\n  }\n\n  if (this.length < other.length) {\n    a = this\n    b = other\n  } else {\n    a = other\n    b = this\n  }\n\n  elements = Object.keys(a.elements)\n\n  for (var i = 0; i < elements.length; i++) {\n    var element = elements[i]\n    if (element in b.elements) {\n      intersection.push(element)\n    }\n  }\n\n  return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n  if (other === lunr.Set.complete) {\n    return lunr.Set.complete\n  }\n\n  if (other === lunr.Set.empty) {\n    return this\n  }\n\n  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n  var documentsWithTerm = 0\n\n  for (var fieldName in posting) {\n    if (fieldName == '_index') continue // Ignore the term index, its not a field\n    documentsWithTerm += Object.keys(posting[fieldName]).length\n  }\n\n  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n  return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n  this.str = str || \"\"\n  this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n  return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n *   return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n  this.str = fn(this.str, this.metadata)\n  return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n  fn = fn || function (s) { return s }\n  return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n  if (obj == null || obj == undefined) {\n    return []\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.map(function (t) {\n      return new lunr.Token(\n        lunr.utils.asString(t).toLowerCase(),\n        lunr.utils.clone(metadata)\n      )\n    })\n  }\n\n  var str = obj.toString().toLowerCase(),\n      len = str.length,\n      tokens = []\n\n  for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n    var char = str.charAt(sliceEnd),\n        sliceLength = sliceEnd - sliceStart\n\n    if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n      if (sliceLength > 0) {\n        var tokenMetadata = lunr.utils.clone(metadata) || {}\n        tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n        tokenMetadata[\"index\"] = tokens.length\n\n        tokens.push(\n          new lunr.Token (\n            str.slice(sliceStart, sliceEnd),\n            tokenMetadata\n          )\n        )\n      }\n\n      sliceStart = sliceEnd + 1\n    }\n\n  }\n\n  return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n  this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n  if (label in this.registeredFunctions) {\n    lunr.utils.warn('Overwriting existing registered function: ' + label)\n  }\n\n  fn.label = label\n  lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n  var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n  if (!isRegistered) {\n    lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n  }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n  var pipeline = new lunr.Pipeline\n\n  serialised.forEach(function (fnName) {\n    var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n    if (fn) {\n      pipeline.add(fn)\n    } else {\n      throw new Error('Cannot load unregistered function: ' + fnName)\n    }\n  })\n\n  return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n  var fns = Array.prototype.slice.call(arguments)\n\n  fns.forEach(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n    this._stack.push(fn)\n  }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  pos = pos + 1\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n  var pos = this._stack.indexOf(fn)\n  if (pos == -1) {\n    return\n  }\n\n  this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n  var stackLength = this._stack.length\n\n  for (var i = 0; i < stackLength; i++) {\n    var fn = this._stack[i]\n    var memo = []\n\n    for (var j = 0; j < tokens.length; j++) {\n      var result = fn(tokens[j], j, tokens)\n\n      if (result === null || result === void 0 || result === '') continue\n\n      if (Array.isArray(result)) {\n        for (var k = 0; k < result.length; k++) {\n          memo.push(result[k])\n        }\n      } else {\n        memo.push(result)\n      }\n    }\n\n    tokens = memo\n  }\n\n  return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n  var token = new lunr.Token (str, metadata)\n\n  return this.run([token]).map(function (t) {\n    return t.toString()\n  })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n  this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n  return this._stack.map(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n    return fn.label\n  })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n  this._magnitude = 0\n  this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n  // For an empty vector the tuple can be inserted at the beginning\n  if (this.elements.length == 0) {\n    return 0\n  }\n\n  var start = 0,\n      end = this.elements.length / 2,\n      sliceLength = end - start,\n      pivotPoint = Math.floor(sliceLength / 2),\n      pivotIndex = this.elements[pivotPoint * 2]\n\n  while (sliceLength > 1) {\n    if (pivotIndex < index) {\n      start = pivotPoint\n    }\n\n    if (pivotIndex > index) {\n      end = pivotPoint\n    }\n\n    if (pivotIndex == index) {\n      break\n    }\n\n    sliceLength = end - start\n    pivotPoint = start + Math.floor(sliceLength / 2)\n    pivotIndex = this.elements[pivotPoint * 2]\n  }\n\n  if (pivotIndex == index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex > index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex < index) {\n    return (pivotPoint + 1) * 2\n  }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n  this.upsert(insertIdx, val, function () {\n    throw \"duplicate index\"\n  })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n  this._magnitude = 0\n  var position = this.positionForIndex(insertIdx)\n\n  if (this.elements[position] == insertIdx) {\n    this.elements[position + 1] = fn(this.elements[position + 1], val)\n  } else {\n    this.elements.splice(position, 0, insertIdx, val)\n  }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n  if (this._magnitude) return this._magnitude\n\n  var sumOfSquares = 0,\n      elementsLength = this.elements.length\n\n  for (var i = 1; i < elementsLength; i += 2) {\n    var val = this.elements[i]\n    sumOfSquares += val * val\n  }\n\n  return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n  var dotProduct = 0,\n      a = this.elements, b = otherVector.elements,\n      aLen = a.length, bLen = b.length,\n      aVal = 0, bVal = 0,\n      i = 0, j = 0\n\n  while (i < aLen && j < bLen) {\n    aVal = a[i], bVal = b[j]\n    if (aVal < bVal) {\n      i += 2\n    } else if (aVal > bVal) {\n      j += 2\n    } else if (aVal == bVal) {\n      dotProduct += a[i + 1] * b[j + 1]\n      i += 2\n      j += 2\n    }\n  }\n\n  return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n  return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n  var output = new Array (this.elements.length / 2)\n\n  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n    output[j] = this.elements[i]\n  }\n\n  return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n  return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n  var step2list = {\n      \"ational\" : \"ate\",\n      \"tional\" : \"tion\",\n      \"enci\" : \"ence\",\n      \"anci\" : \"ance\",\n      \"izer\" : \"ize\",\n      \"bli\" : \"ble\",\n      \"alli\" : \"al\",\n      \"entli\" : \"ent\",\n      \"eli\" : \"e\",\n      \"ousli\" : \"ous\",\n      \"ization\" : \"ize\",\n      \"ation\" : \"ate\",\n      \"ator\" : \"ate\",\n      \"alism\" : \"al\",\n      \"iveness\" : \"ive\",\n      \"fulness\" : \"ful\",\n      \"ousness\" : \"ous\",\n      \"aliti\" : \"al\",\n      \"iviti\" : \"ive\",\n      \"biliti\" : \"ble\",\n      \"logi\" : \"log\"\n    },\n\n    step3list = {\n      \"icate\" : \"ic\",\n      \"ative\" : \"\",\n      \"alize\" : \"al\",\n      \"iciti\" : \"ic\",\n      \"ical\" : \"ic\",\n      \"ful\" : \"\",\n      \"ness\" : \"\"\n    },\n\n    c = \"[^aeiou]\",          // consonant\n    v = \"[aeiouy]\",          // vowel\n    C = c + \"[^aeiouy]*\",    // consonant sequence\n    V = v + \"[aeiou]*\",      // vowel sequence\n\n    mgr0 = \"^(\" + C + \")?\" + V + C,               // [C]VC... is m>0\n    meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\",  // [C]VC[V] is m=1\n    mgr1 = \"^(\" + C + \")?\" + V + C + V + C,       // [C]VCVC... is m>1\n    s_v = \"^(\" + C + \")?\" + v;                   // vowel in stem\n\n  var re_mgr0 = new RegExp(mgr0);\n  var re_mgr1 = new RegExp(mgr1);\n  var re_meq1 = new RegExp(meq1);\n  var re_s_v = new RegExp(s_v);\n\n  var re_1a = /^(.+?)(ss|i)es$/;\n  var re2_1a = /^(.+?)([^s])s$/;\n  var re_1b = /^(.+?)eed$/;\n  var re2_1b = /^(.+?)(ed|ing)$/;\n  var re_1b_2 = /.$/;\n  var re2_1b_2 = /(at|bl|iz)$/;\n  var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n  var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var re_1c = /^(.+?[^aeiou])y$/;\n  var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n  var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n  var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n  var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n  var re_5 = /^(.+?)e$/;\n  var re_5_1 = /ll$/;\n  var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var porterStemmer = function porterStemmer(w) {\n    var stem,\n      suffix,\n      firstch,\n      re,\n      re2,\n      re3,\n      re4;\n\n    if (w.length < 3) { return w; }\n\n    firstch = w.substr(0,1);\n    if (firstch == \"y\") {\n      w = firstch.toUpperCase() + w.substr(1);\n    }\n\n    // Step 1a\n    re = re_1a\n    re2 = re2_1a;\n\n    if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n    else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n    // Step 1b\n    re = re_1b;\n    re2 = re2_1b;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      re = re_mgr0;\n      if (re.test(fp[1])) {\n        re = re_1b_2;\n        w = w.replace(re,\"\");\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1];\n      re2 = re_s_v;\n      if (re2.test(stem)) {\n        w = stem;\n        re2 = re2_1b_2;\n        re3 = re3_1b_2;\n        re4 = re4_1b_2;\n        if (re2.test(w)) { w = w + \"e\"; }\n        else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n        else if (re4.test(w)) { w = w + \"e\"; }\n      }\n    }\n\n    // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n    re = re_1c;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      w = stem + \"i\";\n    }\n\n    // Step 2\n    re = re_2;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step2list[suffix];\n      }\n    }\n\n    // Step 3\n    re = re_3;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step3list[suffix];\n      }\n    }\n\n    // Step 4\n    re = re_4;\n    re2 = re2_4;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      if (re.test(stem)) {\n        w = stem;\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1] + fp[2];\n      re2 = re_mgr1;\n      if (re2.test(stem)) {\n        w = stem;\n      }\n    }\n\n    // Step 5\n    re = re_5;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      re2 = re_meq1;\n      re3 = re3_5;\n      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n        w = stem;\n      }\n    }\n\n    re = re_5_1;\n    re2 = re_mgr1;\n    if (re.test(w) && re2.test(w)) {\n      re = re_1b_2;\n      w = w.replace(re,\"\");\n    }\n\n    // and turn initial Y back to y\n\n    if (firstch == \"y\") {\n      w = firstch.toLowerCase() + w.substr(1);\n    }\n\n    return w;\n  };\n\n  return function (token) {\n    return token.update(porterStemmer);\n  }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n  var words = stopWords.reduce(function (memo, stopWord) {\n    memo[stopWord] = stopWord\n    return memo\n  }, {})\n\n  return function (token) {\n    if (token && words[token.toString()] !== token.toString()) return token\n  }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n  'a',\n  'able',\n  'about',\n  'across',\n  'after',\n  'all',\n  'almost',\n  'also',\n  'am',\n  'among',\n  'an',\n  'and',\n  'any',\n  'are',\n  'as',\n  'at',\n  'be',\n  'because',\n  'been',\n  'but',\n  'by',\n  'can',\n  'cannot',\n  'could',\n  'dear',\n  'did',\n  'do',\n  'does',\n  'either',\n  'else',\n  'ever',\n  'every',\n  'for',\n  'from',\n  'get',\n  'got',\n  'had',\n  'has',\n  'have',\n  'he',\n  'her',\n  'hers',\n  'him',\n  'his',\n  'how',\n  'however',\n  'i',\n  'if',\n  'in',\n  'into',\n  'is',\n  'it',\n  'its',\n  'just',\n  'least',\n  'let',\n  'like',\n  'likely',\n  'may',\n  'me',\n  'might',\n  'most',\n  'must',\n  'my',\n  'neither',\n  'no',\n  'nor',\n  'not',\n  'of',\n  'off',\n  'often',\n  'on',\n  'only',\n  'or',\n  'other',\n  'our',\n  'own',\n  'rather',\n  'said',\n  'say',\n  'says',\n  'she',\n  'should',\n  'since',\n  'so',\n  'some',\n  'than',\n  'that',\n  'the',\n  'their',\n  'them',\n  'then',\n  'there',\n  'these',\n  'they',\n  'this',\n  'tis',\n  'to',\n  'too',\n  'twas',\n  'us',\n  'wants',\n  'was',\n  'we',\n  'were',\n  'what',\n  'when',\n  'where',\n  'which',\n  'while',\n  'who',\n  'whom',\n  'why',\n  'will',\n  'with',\n  'would',\n  'yet',\n  'you',\n  'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n  return token.update(function (s) {\n    return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n  })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n  this.final = false\n  this.edges = {}\n  this.id = lunr.TokenSet._nextId\n  lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n  var builder = new lunr.TokenSet.Builder\n\n  for (var i = 0, len = arr.length; i < len; i++) {\n    builder.insert(arr[i])\n  }\n\n  builder.finish()\n  return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n  if ('editDistance' in clause) {\n    return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n  } else {\n    return lunr.TokenSet.fromString(clause.term)\n  }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n  var root = new lunr.TokenSet\n\n  var stack = [{\n    node: root,\n    editsRemaining: editDistance,\n    str: str\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop()\n\n    // no edit\n    if (frame.str.length > 0) {\n      var char = frame.str.charAt(0),\n          noEditNode\n\n      if (char in frame.node.edges) {\n        noEditNode = frame.node.edges[char]\n      } else {\n        noEditNode = new lunr.TokenSet\n        frame.node.edges[char] = noEditNode\n      }\n\n      if (frame.str.length == 1) {\n        noEditNode.final = true\n      }\n\n      stack.push({\n        node: noEditNode,\n        editsRemaining: frame.editsRemaining,\n        str: frame.str.slice(1)\n      })\n    }\n\n    if (frame.editsRemaining == 0) {\n      continue\n    }\n\n    // insertion\n    if (\"*\" in frame.node.edges) {\n      var insertionNode = frame.node.edges[\"*\"]\n    } else {\n      var insertionNode = new lunr.TokenSet\n      frame.node.edges[\"*\"] = insertionNode\n    }\n\n    if (frame.str.length == 0) {\n      insertionNode.final = true\n    }\n\n    stack.push({\n      node: insertionNode,\n      editsRemaining: frame.editsRemaining - 1,\n      str: frame.str\n    })\n\n    // deletion\n    // can only do a deletion if we have enough edits remaining\n    // and if there are characters left to delete in the string\n    if (frame.str.length > 1) {\n      stack.push({\n        node: frame.node,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // deletion\n    // just removing the last character from the str\n    if (frame.str.length == 1) {\n      frame.node.final = true\n    }\n\n    // substitution\n    // can only do a substitution if we have enough edits remaining\n    // and if there are characters left to substitute\n    if (frame.str.length >= 1) {\n      if (\"*\" in frame.node.edges) {\n        var substitutionNode = frame.node.edges[\"*\"]\n      } else {\n        var substitutionNode = new lunr.TokenSet\n        frame.node.edges[\"*\"] = substitutionNode\n      }\n\n      if (frame.str.length == 1) {\n        substitutionNode.final = true\n      }\n\n      stack.push({\n        node: substitutionNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // transposition\n    // can only do a transposition if there are edits remaining\n    // and there are enough characters to transpose\n    if (frame.str.length > 1) {\n      var charA = frame.str.charAt(0),\n          charB = frame.str.charAt(1),\n          transposeNode\n\n      if (charB in frame.node.edges) {\n        transposeNode = frame.node.edges[charB]\n      } else {\n        transposeNode = new lunr.TokenSet\n        frame.node.edges[charB] = transposeNode\n      }\n\n      if (frame.str.length == 1) {\n        transposeNode.final = true\n      }\n\n      stack.push({\n        node: transposeNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: charA + frame.str.slice(2)\n      })\n    }\n  }\n\n  return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n  var node = new lunr.TokenSet,\n      root = node\n\n  /*\n   * Iterates through all characters within the passed string\n   * appending a node for each character.\n   *\n   * When a wildcard character is found then a self\n   * referencing edge is introduced to continually match\n   * any number of any characters.\n   */\n  for (var i = 0, len = str.length; i < len; i++) {\n    var char = str[i],\n        final = (i == len - 1)\n\n    if (char == \"*\") {\n      node.edges[char] = node\n      node.final = final\n\n    } else {\n      var next = new lunr.TokenSet\n      next.final = final\n\n      node.edges[char] = next\n      node = next\n    }\n  }\n\n  return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n  var words = []\n\n  var stack = [{\n    prefix: \"\",\n    node: this\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop(),\n        edges = Object.keys(frame.node.edges),\n        len = edges.length\n\n    if (frame.node.final) {\n      /* In Safari, at this point the prefix is sometimes corrupted, see:\n       * https://github.com/olivernn/lunr.js/issues/279 Calling any\n       * String.prototype method forces Safari to \"cast\" this string to what\n       * it's supposed to be, fixing the bug. */\n      frame.prefix.charAt(0)\n      words.push(frame.prefix)\n    }\n\n    for (var i = 0; i < len; i++) {\n      var edge = edges[i]\n\n      stack.push({\n        prefix: frame.prefix.concat(edge),\n        node: frame.node.edges[edge]\n      })\n    }\n  }\n\n  return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n  // NOTE: Using Object.keys here as this.edges is very likely\n  // to enter 'hash-mode' with many keys being added\n  //\n  // avoiding a for-in loop here as it leads to the function\n  // being de-optimised (at least in V8). From some simple\n  // benchmarks the performance is comparable, but allowing\n  // V8 to optimize may mean easy performance wins in the future.\n\n  if (this._str) {\n    return this._str\n  }\n\n  var str = this.final ? '1' : '0',\n      labels = Object.keys(this.edges).sort(),\n      len = labels.length\n\n  for (var i = 0; i < len; i++) {\n    var label = labels[i],\n        node = this.edges[label]\n\n    str = str + label + node.id\n  }\n\n  return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n  var output = new lunr.TokenSet,\n      frame = undefined\n\n  var stack = [{\n    qNode: b,\n    output: output,\n    node: this\n  }]\n\n  while (stack.length) {\n    frame = stack.pop()\n\n    // NOTE: As with the #toString method, we are using\n    // Object.keys and a for loop instead of a for-in loop\n    // as both of these objects enter 'hash' mode, causing\n    // the function to be de-optimised in V8\n    var qEdges = Object.keys(frame.qNode.edges),\n        qLen = qEdges.length,\n        nEdges = Object.keys(frame.node.edges),\n        nLen = nEdges.length\n\n    for (var q = 0; q < qLen; q++) {\n      var qEdge = qEdges[q]\n\n      for (var n = 0; n < nLen; n++) {\n        var nEdge = nEdges[n]\n\n        if (nEdge == qEdge || qEdge == '*') {\n          var node = frame.node.edges[nEdge],\n              qNode = frame.qNode.edges[qEdge],\n              final = node.final && qNode.final,\n              next = undefined\n\n          if (nEdge in frame.output.edges) {\n            // an edge already exists for this character\n            // no need to create a new node, just set the finality\n            // bit unless this node is already final\n            next = frame.output.edges[nEdge]\n            next.final = next.final || final\n\n          } else {\n            // no edge exists yet, must create one\n            // set the finality bit and insert it\n            // into the output\n            next = new lunr.TokenSet\n            next.final = final\n            frame.output.edges[nEdge] = next\n          }\n\n          stack.push({\n            qNode: qNode,\n            output: next,\n            node: node\n          })\n        }\n      }\n    }\n  }\n\n  return output\n}\nlunr.TokenSet.Builder = function () {\n  this.previousWord = \"\"\n  this.root = new lunr.TokenSet\n  this.uncheckedNodes = []\n  this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n  var node,\n      commonPrefix = 0\n\n  if (word < this.previousWord) {\n    throw new Error (\"Out of order word insertion\")\n  }\n\n  for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n    if (word[i] != this.previousWord[i]) break\n    commonPrefix++\n  }\n\n  this.minimize(commonPrefix)\n\n  if (this.uncheckedNodes.length == 0) {\n    node = this.root\n  } else {\n    node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n  }\n\n  for (var i = commonPrefix; i < word.length; i++) {\n    var nextNode = new lunr.TokenSet,\n        char = word[i]\n\n    node.edges[char] = nextNode\n\n    this.uncheckedNodes.push({\n      parent: node,\n      char: char,\n      child: nextNode\n    })\n\n    node = nextNode\n  }\n\n  node.final = true\n  this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n  this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n  for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n    var node = this.uncheckedNodes[i],\n        childKey = node.child.toString()\n\n    if (childKey in this.minimizedNodes) {\n      node.parent.edges[node.char] = this.minimizedNodes[childKey]\n    } else {\n      // Cache the key for this node since\n      // we know it can't change anymore\n      node.child._str = childKey\n\n      this.minimizedNodes[childKey] = node.child\n    }\n\n    this.uncheckedNodes.pop()\n  }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n  this.invertedIndex = attrs.invertedIndex\n  this.fieldVectors = attrs.fieldVectors\n  this.tokenSet = attrs.tokenSet\n  this.fields = attrs.fields\n  this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example <caption>Simple single term query</caption>\n * hello\n * @example <caption>Multiple term query</caption>\n * hello world\n * @example <caption>term scoped to a field</caption>\n * title:hello\n * @example <caption>term with a boost of 10</caption>\n * hello^10\n * @example <caption>term with an edit distance of 2</caption>\n * hello~2\n * @example <caption>terms with presence modifiers</caption>\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first.  For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n  return this.query(function (query) {\n    var parser = new lunr.QueryParser(queryString, query)\n    parser.parse()\n  })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n  // for each query clause\n  // * process terms\n  // * expand terms from token set\n  // * find matching documents and metadata\n  // * get document vectors\n  // * score documents\n\n  var query = new lunr.Query(this.fields),\n      matchingFields = Object.create(null),\n      queryVectors = Object.create(null),\n      termFieldCache = Object.create(null),\n      requiredMatches = Object.create(null),\n      prohibitedMatches = Object.create(null)\n\n  /*\n   * To support field level boosts a query vector is created per\n   * field. An empty vector is eagerly created to support negated\n   * queries.\n   */\n  for (var i = 0; i < this.fields.length; i++) {\n    queryVectors[this.fields[i]] = new lunr.Vector\n  }\n\n  fn.call(query, query)\n\n  for (var i = 0; i < query.clauses.length; i++) {\n    /*\n     * Unless the pipeline has been disabled for this term, which is\n     * the case for terms with wildcards, we need to pass the clause\n     * term through the search pipeline. A pipeline returns an array\n     * of processed terms. Pipeline functions may expand the passed\n     * term, which means we may end up performing multiple index lookups\n     * for a single query term.\n     */\n    var clause = query.clauses[i],\n        terms = null,\n        clauseMatches = lunr.Set.empty\n\n    if (clause.usePipeline) {\n      terms = this.pipeline.runString(clause.term, {\n        fields: clause.fields\n      })\n    } else {\n      terms = [clause.term]\n    }\n\n    for (var m = 0; m < terms.length; m++) {\n      var term = terms[m]\n\n      /*\n       * Each term returned from the pipeline needs to use the same query\n       * clause object, e.g. the same boost and or edit distance. The\n       * simplest way to do this is to re-use the clause object but mutate\n       * its term property.\n       */\n      clause.term = term\n\n      /*\n       * From the term in the clause we create a token set which will then\n       * be used to intersect the indexes token set to get a list of terms\n       * to lookup in the inverted index\n       */\n      var termTokenSet = lunr.TokenSet.fromClause(clause),\n          expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n      /*\n       * If a term marked as required does not exist in the tokenSet it is\n       * impossible for the search to return any matches. We set all the field\n       * scoped required matches set to empty and stop examining any further\n       * clauses.\n       */\n      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n        for (var k = 0; k < clause.fields.length; k++) {\n          var field = clause.fields[k]\n          requiredMatches[field] = lunr.Set.empty\n        }\n\n        break\n      }\n\n      for (var j = 0; j < expandedTerms.length; j++) {\n        /*\n         * For each term get the posting and termIndex, this is required for\n         * building the query vector.\n         */\n        var expandedTerm = expandedTerms[j],\n            posting = this.invertedIndex[expandedTerm],\n            termIndex = posting._index\n\n        for (var k = 0; k < clause.fields.length; k++) {\n          /*\n           * For each field that this query term is scoped by (by default\n           * all fields are in scope) we need to get all the document refs\n           * that have this term in that field.\n           *\n           * The posting is the entry in the invertedIndex for the matching\n           * term from above.\n           */\n          var field = clause.fields[k],\n              fieldPosting = posting[field],\n              matchingDocumentRefs = Object.keys(fieldPosting),\n              termField = expandedTerm + \"/\" + field,\n              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n          /*\n           * if the presence of this term is required ensure that the matching\n           * documents are added to the set of required matches for this clause.\n           *\n           */\n          if (clause.presence == lunr.Query.presence.REQUIRED) {\n            clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n            if (requiredMatches[field] === undefined) {\n              requiredMatches[field] = lunr.Set.complete\n            }\n          }\n\n          /*\n           * if the presence of this term is prohibited ensure that the matching\n           * documents are added to the set of prohibited matches for this field,\n           * creating that set if it does not yet exist.\n           */\n          if (clause.presence == lunr.Query.presence.PROHIBITED) {\n            if (prohibitedMatches[field] === undefined) {\n              prohibitedMatches[field] = lunr.Set.empty\n            }\n\n            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n            /*\n             * Prohibited matches should not be part of the query vector used for\n             * similarity scoring and no metadata should be extracted so we continue\n             * to the next field\n             */\n            continue\n          }\n\n          /*\n           * The query field vector is populated using the termIndex found for\n           * the term and a unit value with the appropriate boost applied.\n           * Using upsert because there could already be an entry in the vector\n           * for the term we are working with. In that case we just add the scores\n           * together.\n           */\n          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n          /**\n           * If we've already seen this term, field combo then we've already collected\n           * the matching documents and metadata, no need to go through all that again\n           */\n          if (termFieldCache[termField]) {\n            continue\n          }\n\n          for (var l = 0; l < matchingDocumentRefs.length; l++) {\n            /*\n             * All metadata for this term/field/document triple\n             * are then extracted and collected into an instance\n             * of lunr.MatchData ready to be returned in the query\n             * results\n             */\n            var matchingDocumentRef = matchingDocumentRefs[l],\n                matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n                metadata = fieldPosting[matchingDocumentRef],\n                fieldMatch\n\n            if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n              matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n            } else {\n              fieldMatch.add(expandedTerm, field, metadata)\n            }\n\n          }\n\n          termFieldCache[termField] = true\n        }\n      }\n    }\n\n    /**\n     * If the presence was required we need to update the requiredMatches field sets.\n     * We do this after all fields for the term have collected their matches because\n     * the clause terms presence is required in _any_ of the fields not _all_ of the\n     * fields.\n     */\n    if (clause.presence === lunr.Query.presence.REQUIRED) {\n      for (var k = 0; k < clause.fields.length; k++) {\n        var field = clause.fields[k]\n        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n      }\n    }\n  }\n\n  /**\n   * Need to combine the field scoped required and prohibited\n   * matching documents into a global set of required and prohibited\n   * matches\n   */\n  var allRequiredMatches = lunr.Set.complete,\n      allProhibitedMatches = lunr.Set.empty\n\n  for (var i = 0; i < this.fields.length; i++) {\n    var field = this.fields[i]\n\n    if (requiredMatches[field]) {\n      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n    }\n\n    if (prohibitedMatches[field]) {\n      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n    }\n  }\n\n  var matchingFieldRefs = Object.keys(matchingFields),\n      results = [],\n      matches = Object.create(null)\n\n  /*\n   * If the query is negated (contains only prohibited terms)\n   * we need to get _all_ fieldRefs currently existing in the\n   * index. This is only done when we know that the query is\n   * entirely prohibited terms to avoid any cost of getting all\n   * fieldRefs unnecessarily.\n   *\n   * Additionally, blank MatchData must be created to correctly\n   * populate the results.\n   */\n  if (query.isNegated()) {\n    matchingFieldRefs = Object.keys(this.fieldVectors)\n\n    for (var i = 0; i < matchingFieldRefs.length; i++) {\n      var matchingFieldRef = matchingFieldRefs[i]\n      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n      matchingFields[matchingFieldRef] = new lunr.MatchData\n    }\n  }\n\n  for (var i = 0; i < matchingFieldRefs.length; i++) {\n    /*\n     * Currently we have document fields that match the query, but we\n     * need to return documents. The matchData and scores are combined\n     * from multiple fields belonging to the same document.\n     *\n     * Scores are calculated by field, using the query vectors created\n     * above, and combined into a final document score using addition.\n     */\n    var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n        docRef = fieldRef.docRef\n\n    if (!allRequiredMatches.contains(docRef)) {\n      continue\n    }\n\n    if (allProhibitedMatches.contains(docRef)) {\n      continue\n    }\n\n    var fieldVector = this.fieldVectors[fieldRef],\n        score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n        docMatch\n\n    if ((docMatch = matches[docRef]) !== undefined) {\n      docMatch.score += score\n      docMatch.matchData.combine(matchingFields[fieldRef])\n    } else {\n      var match = {\n        ref: docRef,\n        score: score,\n        matchData: matchingFields[fieldRef]\n      }\n      matches[docRef] = match\n      results.push(match)\n    }\n  }\n\n  /*\n   * Sort the results objects by score, highest first.\n   */\n  return results.sort(function (a, b) {\n    return b.score - a.score\n  })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n  var invertedIndex = Object.keys(this.invertedIndex)\n    .sort()\n    .map(function (term) {\n      return [term, this.invertedIndex[term]]\n    }, this)\n\n  var fieldVectors = Object.keys(this.fieldVectors)\n    .map(function (ref) {\n      return [ref, this.fieldVectors[ref].toJSON()]\n    }, this)\n\n  return {\n    version: lunr.version,\n    fields: this.fields,\n    fieldVectors: fieldVectors,\n    invertedIndex: invertedIndex,\n    pipeline: this.pipeline.toJSON()\n  }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n  var attrs = {},\n      fieldVectors = {},\n      serializedVectors = serializedIndex.fieldVectors,\n      invertedIndex = Object.create(null),\n      serializedInvertedIndex = serializedIndex.invertedIndex,\n      tokenSetBuilder = new lunr.TokenSet.Builder,\n      pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n  if (serializedIndex.version != lunr.version) {\n    lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n  }\n\n  for (var i = 0; i < serializedVectors.length; i++) {\n    var tuple = serializedVectors[i],\n        ref = tuple[0],\n        elements = tuple[1]\n\n    fieldVectors[ref] = new lunr.Vector(elements)\n  }\n\n  for (var i = 0; i < serializedInvertedIndex.length; i++) {\n    var tuple = serializedInvertedIndex[i],\n        term = tuple[0],\n        posting = tuple[1]\n\n    tokenSetBuilder.insert(term)\n    invertedIndex[term] = posting\n  }\n\n  tokenSetBuilder.finish()\n\n  attrs.fields = serializedIndex.fields\n\n  attrs.fieldVectors = fieldVectors\n  attrs.invertedIndex = invertedIndex\n  attrs.tokenSet = tokenSetBuilder.root\n  attrs.pipeline = pipeline\n\n  return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n  this._ref = \"id\"\n  this._fields = Object.create(null)\n  this._documents = Object.create(null)\n  this.invertedIndex = Object.create(null)\n  this.fieldTermFrequencies = {}\n  this.fieldLengths = {}\n  this.tokenizer = lunr.tokenizer\n  this.pipeline = new lunr.Pipeline\n  this.searchPipeline = new lunr.Pipeline\n  this.documentCount = 0\n  this._b = 0.75\n  this._k1 = 1.2\n  this.termIndex = 0\n  this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n  this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example <caption>Extracting a nested field</caption>\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n  if (/\\//.test(fieldName)) {\n    throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n  }\n\n  this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n  if (number < 0) {\n    this._b = 0\n  } else if (number > 1) {\n    this._b = 1\n  } else {\n    this._b = number\n  }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n  this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n  var docRef = doc[this._ref],\n      fields = Object.keys(this._fields)\n\n  this._documents[docRef] = attributes || {}\n  this.documentCount += 1\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i],\n        extractor = this._fields[fieldName].extractor,\n        field = extractor ? extractor(doc) : doc[fieldName],\n        tokens = this.tokenizer(field, {\n          fields: [fieldName]\n        }),\n        terms = this.pipeline.run(tokens),\n        fieldRef = new lunr.FieldRef (docRef, fieldName),\n        fieldTerms = Object.create(null)\n\n    this.fieldTermFrequencies[fieldRef] = fieldTerms\n    this.fieldLengths[fieldRef] = 0\n\n    // store the length of this field for this document\n    this.fieldLengths[fieldRef] += terms.length\n\n    // calculate term frequencies for this field\n    for (var j = 0; j < terms.length; j++) {\n      var term = terms[j]\n\n      if (fieldTerms[term] == undefined) {\n        fieldTerms[term] = 0\n      }\n\n      fieldTerms[term] += 1\n\n      // add to inverted index\n      // create an initial posting if one doesn't exist\n      if (this.invertedIndex[term] == undefined) {\n        var posting = Object.create(null)\n        posting[\"_index\"] = this.termIndex\n        this.termIndex += 1\n\n        for (var k = 0; k < fields.length; k++) {\n          posting[fields[k]] = Object.create(null)\n        }\n\n        this.invertedIndex[term] = posting\n      }\n\n      // add an entry for this term/fieldName/docRef to the invertedIndex\n      if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n        this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n      }\n\n      // store all whitelisted metadata about this token in the\n      // inverted index\n      for (var l = 0; l < this.metadataWhitelist.length; l++) {\n        var metadataKey = this.metadataWhitelist[l],\n            metadata = term.metadata[metadataKey]\n\n        if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n          this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n        }\n\n        this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n      }\n    }\n\n  }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n  var fieldRefs = Object.keys(this.fieldLengths),\n      numberOfFields = fieldRefs.length,\n      accumulator = {},\n      documentsWithField = {}\n\n  for (var i = 0; i < numberOfFields; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        field = fieldRef.fieldName\n\n    documentsWithField[field] || (documentsWithField[field] = 0)\n    documentsWithField[field] += 1\n\n    accumulator[field] || (accumulator[field] = 0)\n    accumulator[field] += this.fieldLengths[fieldRef]\n  }\n\n  var fields = Object.keys(this._fields)\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i]\n    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n  }\n\n  this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n  var fieldVectors = {},\n      fieldRefs = Object.keys(this.fieldTermFrequencies),\n      fieldRefsLength = fieldRefs.length,\n      termIdfCache = Object.create(null)\n\n  for (var i = 0; i < fieldRefsLength; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        fieldName = fieldRef.fieldName,\n        fieldLength = this.fieldLengths[fieldRef],\n        fieldVector = new lunr.Vector,\n        termFrequencies = this.fieldTermFrequencies[fieldRef],\n        terms = Object.keys(termFrequencies),\n        termsLength = terms.length\n\n\n    var fieldBoost = this._fields[fieldName].boost || 1,\n        docBoost = this._documents[fieldRef.docRef].boost || 1\n\n    for (var j = 0; j < termsLength; j++) {\n      var term = terms[j],\n          tf = termFrequencies[term],\n          termIndex = this.invertedIndex[term]._index,\n          idf, score, scoreWithPrecision\n\n      if (termIdfCache[term] === undefined) {\n        idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n        termIdfCache[term] = idf\n      } else {\n        idf = termIdfCache[term]\n      }\n\n      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n      score *= fieldBoost\n      score *= docBoost\n      scoreWithPrecision = Math.round(score * 1000) / 1000\n      // Converts 1.23456789 to 1.234.\n      // Reducing the precision so that the vectors take up less\n      // space when serialised. Doing it now so that they behave\n      // the same before and after serialisation. Also, this is\n      // the fastest approach to reducing a number's precision in\n      // JavaScript.\n\n      fieldVector.insert(termIndex, scoreWithPrecision)\n    }\n\n    fieldVectors[fieldRef] = fieldVector\n  }\n\n  this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n  this.tokenSet = lunr.TokenSet.fromArray(\n    Object.keys(this.invertedIndex).sort()\n  )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n  this.calculateAverageFieldLengths()\n  this.createFieldVectors()\n  this.createTokenSet()\n\n  return new lunr.Index({\n    invertedIndex: this.invertedIndex,\n    fieldVectors: this.fieldVectors,\n    tokenSet: this.tokenSet,\n    fields: Object.keys(this._fields),\n    pipeline: this.searchPipeline\n  })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n  var args = Array.prototype.slice.call(arguments, 1)\n  args.unshift(this)\n  fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n  var clonedMetadata = Object.create(null),\n      metadataKeys = Object.keys(metadata || {})\n\n  // Cloning the metadata to prevent the original\n  // being mutated during match data combination.\n  // Metadata is kept in an array within the inverted\n  // index so cloning the data can be done with\n  // Array#slice\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n    clonedMetadata[key] = metadata[key].slice()\n  }\n\n  this.metadata = Object.create(null)\n\n  if (term !== undefined) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = clonedMetadata\n  }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n  var terms = Object.keys(otherMatchData.metadata)\n\n  for (var i = 0; i < terms.length; i++) {\n    var term = terms[i],\n        fields = Object.keys(otherMatchData.metadata[term])\n\n    if (this.metadata[term] == undefined) {\n      this.metadata[term] = Object.create(null)\n    }\n\n    for (var j = 0; j < fields.length; j++) {\n      var field = fields[j],\n          keys = Object.keys(otherMatchData.metadata[term][field])\n\n      if (this.metadata[term][field] == undefined) {\n        this.metadata[term][field] = Object.create(null)\n      }\n\n      for (var k = 0; k < keys.length; k++) {\n        var key = keys[k]\n\n        if (this.metadata[term][field][key] == undefined) {\n          this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n        } else {\n          this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n        }\n\n      }\n    }\n  }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n  if (!(term in this.metadata)) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  if (!(field in this.metadata[term])) {\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  var metadataKeys = Object.keys(metadata)\n\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n\n    if (key in this.metadata[term][field]) {\n      this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n    } else {\n      this.metadata[term][field][key] = metadata[key]\n    }\n  }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n  this.clauses = []\n  this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with trailing wildcard</caption>\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example <caption>query term with leading and trailing wildcard</caption>\n * query.term('foo', {\n *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with required presence</caption>\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n  /**\n   * Term's presence in a document is optional, this is the default value.\n   */\n  OPTIONAL: 1,\n\n  /**\n   * Term's presence in a document is required, documents that do not contain\n   * this term will not be returned.\n   */\n  REQUIRED: 2,\n\n  /**\n   * Term's presence in a document is prohibited, documents that do contain\n   * this term will not be returned.\n   */\n  PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n  if (!('fields' in clause)) {\n    clause.fields = this.allFields\n  }\n\n  if (!('boost' in clause)) {\n    clause.boost = 1\n  }\n\n  if (!('usePipeline' in clause)) {\n    clause.usePipeline = true\n  }\n\n  if (!('wildcard' in clause)) {\n    clause.wildcard = lunr.Query.wildcard.NONE\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n    clause.term = \"*\" + clause.term\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n    clause.term = \"\" + clause.term + \"*\"\n  }\n\n  if (!('presence' in clause)) {\n    clause.presence = lunr.Query.presence.OPTIONAL\n  }\n\n  this.clauses.push(clause)\n\n  return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n  for (var i = 0; i < this.clauses.length; i++) {\n    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n      return false\n    }\n  }\n\n  return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example <caption>adding a single term to a query</caption>\n * query.term(\"foo\")\n * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>\n * query.term(\"foo\", {\n *   fields: [\"title\"],\n *   boost: 10,\n *   wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n  if (Array.isArray(term)) {\n    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n    return this\n  }\n\n  var clause = options || {}\n  clause.term = term.toString()\n\n  this.clause(clause)\n\n  return this\n}\nlunr.QueryParseError = function (message, start, end) {\n  this.name = \"QueryParseError\"\n  this.message = message\n  this.start = start\n  this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n  this.lexemes = []\n  this.str = str\n  this.length = str.length\n  this.pos = 0\n  this.start = 0\n  this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n  var state = lunr.QueryLexer.lexText\n\n  while (state) {\n    state = state(this)\n  }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n  var subSlices = [],\n      sliceStart = this.start,\n      sliceEnd = this.pos\n\n  for (var i = 0; i < this.escapeCharPositions.length; i++) {\n    sliceEnd = this.escapeCharPositions[i]\n    subSlices.push(this.str.slice(sliceStart, sliceEnd))\n    sliceStart = sliceEnd + 1\n  }\n\n  subSlices.push(this.str.slice(sliceStart, this.pos))\n  this.escapeCharPositions.length = 0\n\n  return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n  this.lexemes.push({\n    type: type,\n    str: this.sliceString(),\n    start: this.start,\n    end: this.pos\n  })\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n  this.escapeCharPositions.push(this.pos - 1)\n  this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n  if (this.pos >= this.length) {\n    return lunr.QueryLexer.EOS\n  }\n\n  var char = this.str.charAt(this.pos)\n  this.pos += 1\n  return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n  return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n  if (this.start == this.pos) {\n    this.pos += 1\n  }\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n  this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n  var char, charCode\n\n  do {\n    char = this.next()\n    charCode = char.charCodeAt(0)\n  } while (charCode > 47 && charCode < 58)\n\n  if (char != lunr.QueryLexer.EOS) {\n    this.backup()\n  }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n  return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n  lexer.backup()\n  lexer.emit(lunr.QueryLexer.FIELD)\n  lexer.ignore()\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n  if (lexer.width() > 1) {\n    lexer.backup()\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n\n  lexer.ignore()\n\n  if (lexer.more()) {\n    return lunr.QueryLexer.lexText\n  }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.BOOST)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n  if (lexer.width() > 0) {\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n  while (true) {\n    var char = lexer.next()\n\n    if (char == lunr.QueryLexer.EOS) {\n      return lunr.QueryLexer.lexEOS\n    }\n\n    // Escape character is '\\'\n    if (char.charCodeAt(0) == 92) {\n      lexer.escapeCharacter()\n      continue\n    }\n\n    if (char == \":\") {\n      return lunr.QueryLexer.lexField\n    }\n\n    if (char == \"~\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexEditDistance\n    }\n\n    if (char == \"^\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexBoost\n    }\n\n    // \"+\" indicates term presence is required\n    // checking for length to ensure that only\n    // leading \"+\" are considered\n    if (char == \"+\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    // \"-\" indicates term presence is prohibited\n    // checking for length to ensure that only\n    // leading \"-\" are considered\n    if (char == \"-\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    if (char.match(lunr.QueryLexer.termSeparator)) {\n      return lunr.QueryLexer.lexTerm\n    }\n  }\n}\n\nlunr.QueryParser = function (str, query) {\n  this.lexer = new lunr.QueryLexer (str)\n  this.query = query\n  this.currentClause = {}\n  this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n  this.lexer.run()\n  this.lexemes = this.lexer.lexemes\n\n  var state = lunr.QueryParser.parseClause\n\n  while (state) {\n    state = state(this)\n  }\n\n  return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n  return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n  var lexeme = this.peekLexeme()\n  this.lexemeIdx += 1\n  return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n  var completedClause = this.currentClause\n  this.query.clause(completedClause)\n  this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n  var lexeme = parser.peekLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.type) {\n    case lunr.QueryLexer.PRESENCE:\n      return lunr.QueryParser.parsePresence\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n      if (lexeme.str.length >= 1) {\n        errorMessage += \" with value '\" + lexeme.str + \"'\"\n      }\n\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.str) {\n    case \"-\":\n      parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n      break\n    case \"+\":\n      parser.currentClause.presence = lunr.Query.presence.REQUIRED\n      break\n    default:\n      var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term or field, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n    var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n        errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.fields = [lexeme.str]\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  parser.currentClause.term = lexeme.str.toLowerCase()\n\n  if (lexeme.str.indexOf(\"*\") != -1) {\n    parser.currentClause.usePipeline = false\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var editDistance = parseInt(lexeme.str, 10)\n\n  if (isNaN(editDistance)) {\n    var errorMessage = \"edit distance must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.editDistance = editDistance\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var boost = parseInt(lexeme.str, 10)\n\n  if (isNaN(boost)) {\n    var errorMessage = \"boost must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.boost = boost\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\n  /**\n   * export the module via AMD, CommonJS or as a browser global\n   * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n   */\n  ;(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n      // AMD. Register as an anonymous module.\n      define(factory)\n    } else if (typeof exports === 'object') {\n      /**\n       * Node. Does not work with strict CommonJS, but\n       * only CommonJS-like enviroments that support module.exports,\n       * like Node.\n       */\n      module.exports = factory()\n    } else {\n      // Browser globals (root is window)\n      root.lunr = factory()\n    }\n  }(this, function () {\n    /**\n     * Just return a value to define the module export.\n     * This example returns an object, but the module\n     * can return a function as the exported value.\n     */\n    return lunr\n  }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n  function importScripts(...urls: string[]): Promise<void> | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n  config: SearchIndexConfig\n): Promise<void> {\n  let base = \"../lunr\"\n\n  /* Detect `iframe-worker` and fix base URL */\n  if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n    const worker = document.querySelector<HTMLScriptElement>(\"script[src]\")!\n    const [path] = worker.src.split(\"/worker\")\n\n    /* Prefix base with path */\n    base = base.replace(\"..\", path)\n  }\n\n  /* Add scripts for languages */\n  const scripts = []\n  for (const lang of config.lang) {\n    if (lang === \"ja\") scripts.push(`${base}/tinyseg.js`)\n    if (lang !== \"en\") scripts.push(`${base}/min/lunr.${lang}.min.js`)\n  }\n\n  /* Add multi-language support */\n  if (config.lang.length > 1)\n    scripts.push(`${base}/min/lunr.multi.min.js`)\n\n  /* Load scripts synchronously */\n  if (scripts.length)\n    await importScripts(\n      `${base}/min/lunr.stemmer.support.min.js`,\n      ...scripts\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n  message: SearchMessage\n): Promise<SearchMessage> {\n  switch (message.type) {\n\n    /* Search setup message */\n    case SearchMessageType.SETUP:\n      await setupSearchLanguages(message.data.config)\n      index = new Search(message.data)\n      return {\n        type: SearchMessageType.READY\n      }\n\n    /* Search query message */\n    case SearchMessageType.QUERY:\n      return {\n        type: SearchMessageType.RESULT,\n        data: index ? index.search(message.data) : []\n      }\n\n    /* All other messages */\n    default:\n      throw new TypeError(\"Invalid message type\")\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-ignore - expose Lunr.js in global scope, or stemmers will not work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n  postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n  config: SearchIndexConfig\n): SearchHighlightFactoryFn {\n  const separator = new RegExp(config.separator, \"img\")\n  const highlight = (_: unknown, data: string, term: string) => {\n    return `${data}<mark data-md-highlight>${term}</mark>`\n  }\n\n  /* Return factory function */\n  return (query: string) => {\n    query = query\n      .replace(/[\\s*+\\-:~^]+/g, \" \")\n      .trim()\n\n    /* Create search term match expression */\n    const match = new RegExp(`(^|${config.separator})(${\n      query\n        .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n        .replace(separator, \"|\")\n    })`, \"img\")\n\n    /* Highlight string value */\n    return value => value\n      .replace(match, highlight)\n      .replace(/<\\/mark>(\\s+)<mark[^>]*>/img, \"$1\")\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n  presence: lunr.Query.presence        /* Clause presence */\n  term: string                         /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record<string, boolean>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n  value: string\n): SearchQueryClause[] {\n  const query  = new (lunr as any).Query([\"title\", \"text\"])\n  const parser = new (lunr as any).QueryParser(value, query)\n\n  /* Parse and return query clauses */\n  parser.parse()\n  return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n  query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n  const clauses = new Set<SearchQueryClause>(query)\n\n  /* Match query clauses against terms */\n  const result: SearchQueryTerms = {}\n  for (let t = 0; t < terms.length; t++)\n    for (const clause of clauses)\n      if (terms[t].startsWith(clause.term)) {\n        result[clause.term] = true\n        clauses.delete(clause)\n      }\n\n  /* Annotate unmatched query clauses */\n  for (const clause of clauses)\n    result[clause.term] = false\n\n  /* Return query terms */\n  return result\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  SearchDocument,\n  SearchDocumentMap,\n  setupSearchDocumentMap\n} from \"../document\"\nimport {\n  SearchHighlightFactoryFn,\n  setupSearchHighlighter\n} from \"../highlighter\"\nimport {\n  SearchQueryTerms,\n  getSearchQueryTerms,\n  parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n  lang: string[]                       /* Search languages */\n  separator: string                    /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n  location: string                     /* Document location */\n  title: string                        /* Document title */\n  text: string                         /* Document text */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index pipeline function\n */\nexport type SearchIndexPipelineFn =\n  | \"trimmer\"                          /* Trimmer */\n  | \"stopWordFilter\"                   /* Stop word filter */\n  | \"stemmer\"                          /* Stemmer */\n\n/**\n * Search index pipeline\n */\nexport type SearchIndexPipeline = SearchIndexPipelineFn[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n  config: SearchIndexConfig            /* Search index configuration */\n  docs: SearchIndexDocument[]          /* Search index documents */\n  index?: object                       /* Prebuilt index */\n  pipeline?: SearchIndexPipeline       /* Search index pipeline */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n  score: number                        /* Score (relevance) */\n  terms: SearchQueryTerms              /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport type SearchResult = Array<SearchDocument & SearchMetadata>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n  const [x, y] = [new Set(a), new Set(b)]\n  return [\n    ...new Set([...x].filter(value => !y.has(value)))\n  ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n  /**\n   * Search document mapping\n   *\n   * A mapping of URLs (including hash fragments) to the actual articles and\n   * sections of the documentation. The search document mapping must be created\n   * regardless of whether the index was prebuilt or not, as Lunr.js itself\n   * only stores the actual index.\n   */\n  protected documents: SearchDocumentMap\n\n  /**\n   * Search highlight factory function\n   */\n  protected highlight: SearchHighlightFactoryFn\n\n  /**\n   * The underlying Lunr.js search index\n   */\n  protected index: lunr.Index\n\n  /**\n   * Create the search integration\n   *\n   * @param data - Search index\n   */\n  public constructor({ config, docs, pipeline, index }: SearchIndex) {\n    this.documents = setupSearchDocumentMap(docs)\n    this.highlight = setupSearchHighlighter(config)\n\n    /* Set separator for tokenizer */\n    lunr.tokenizer.separator = new RegExp(config.separator)\n\n    /* If no index was given, create it */\n    if (typeof index === \"undefined\") {\n      this.index = lunr(function () {\n\n        /* Set up multi-language support */\n        if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n          this.use((lunr as any)[config.lang[0]])\n        } else if (config.lang.length > 1) {\n          this.use((lunr as any).multiLanguage(...config.lang))\n        }\n\n        /* Compute functions to be removed from the pipeline */\n        const fns = difference([\n          \"trimmer\", \"stopWordFilter\", \"stemmer\"\n        ], pipeline!)\n\n        /* Remove functions from the pipeline for registered languages */\n        for (const lang of config.lang.map(language => (\n          language === \"en\" ? lunr : (lunr as any)[language]\n        ))) {\n          for (const fn of fns) {\n            this.pipeline.remove(lang[fn])\n            this.searchPipeline.remove(lang[fn])\n          }\n        }\n\n        /* Set up fields and reference */\n        this.field(\"title\", { boost: 1000 })\n        this.field(\"text\")\n        this.ref(\"location\")\n\n        /* Index documents */\n        for (const doc of docs)\n          this.add(doc)\n      })\n\n    /* Handle prebuilt index */\n    } else {\n      this.index = lunr.Index.load(index)\n    }\n  }\n\n  /**\n   * Search for matching documents\n   *\n   * The search index which MkDocs provides is divided up into articles, which\n   * contain the whole content of the individual pages, and sections, which only\n   * contain the contents of the subsections obtained by breaking the individual\n   * pages up at `h1` ... `h6`. As there may be many sections on different pages\n   * with identical titles (for example within this very project, e.g. \"Usage\"\n   * or \"Installation\"), they need to be put into the context of the containing\n   * page. For this reason, section results are grouped within their respective\n   * articles which are the top-level results that are returned.\n   *\n   * @param query - Query value\n   *\n   * @returns Search results\n   */\n  public search(query: string): SearchResult[] {\n    if (query) {\n      try {\n        const highlight = this.highlight(query)\n\n        /* Parse query to extract clauses for analysis */\n        const clauses = parseSearchQuery(query)\n          .filter(clause => (\n            clause.presence !== lunr.Query.presence.PROHIBITED\n          ))\n\n        /* Perform search and post-process results */\n        const groups = this.index.search(`${query}*`)\n\n          /* Apply post-query boosts based on title and search query terms */\n          .reduce<SearchResult>((results, { ref, score, matchData }) => {\n            const document = this.documents.get(ref)\n            if (typeof document !== \"undefined\") {\n              const { location, title, text, parent } = document\n\n              /* Compute and analyze search query terms */\n              const terms = getSearchQueryTerms(\n                clauses,\n                Object.keys(matchData.metadata)\n              )\n\n              /* Highlight title and text and apply post-query boosts */\n              const boost = +!parent + +Object.values(terms).every(t => t)\n              results.push({\n                location,\n                title: highlight(title),\n                text: highlight(text),\n                score: score * (1 + boost),\n                terms\n              })\n            }\n            return results\n          }, [])\n\n          /* Sort search results again after applying boosts */\n          .sort((a, b) => b.score - a.score)\n\n          /* Group search results by page */\n          .reduce((results, result) => {\n            const document = this.documents.get(result.location)\n            if (typeof document !== \"undefined\") {\n              const ref = \"parent\" in document\n                ? document.parent!.location\n                : document.location\n              results.set(ref, [...results.get(ref) || [], result])\n            }\n            return results\n          }, new Map<string, SearchResult>())\n\n        /* Expand grouped search results */\n        return [...groups.values()]\n\n      /* Log errors to console (for now) */\n      } catch {\n        console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n      }\n    }\n\n    /* Return nothing in case of error or empty query */\n    return []\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n"],
-  "mappings": "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AAG,GACV,EAAK,EACD,EAAG,KAAK,IACV,GAAI,WAEG,EAAI,KAAK,GAAI,CACtB,GAAI,GAAK,EAAI,KAAK,GAClB,EAAO,EAAG,GAAK,EAAG,GAClB,EAAM,EACF,EAAI,KAAK,IACX,GAAI,GAMR,GADA,EAAK,EACD,EAAG,KAAK,GAAI,CACd,GAAI,GAAK,EAAG,KAAK,GACjB,EAAO,EAAG,GACV,EAAK,EACL,EAAM,EACN,EAAM,EACF,GAAG,KAAK,IAAU,EAAI,KAAK,IAAS,CAAE,EAAI,KAAK,KACjD,GAAI,GAIR,SAAK,EACL,EAAM,EACF,EAAG,KAAK,IAAM,EAAI,KAAK,IACzB,GAAK,EACL,EAAI,EAAE,QAAQ,EAAG,KAKf,GAAW,KACb,GAAI,EAAQ,cAAgB,EAAE,OAAO,IAGhC,GAGT,MAAO,UAAU,EAAO,CACtB,MAAO,GAAM,OAAO,OAIxB,EAAK,SAAS,iBAAiB,EAAK,QAAS,WAC7C,AAkBA,EAAK,uBAAyB,SAAU,EAAW,CACjD,GAAI,GAAQ,EAAU,OAAO,SAAU,EAAM,EAAU,CACrD,SAAK,GAAY,EACV,GACN,IAEH,MAAO,UAAU,EAAO,CACtB,GAAI,GAAS,EAAM,EAAM,cAAgB,EAAM,WAAY,MAAO,KAiBtE,EAAK,eAAiB,EAAK,uBAAuB,CAChD,IACA,OACA,QACA,SACA,QACA,MACA,SACA,OACA,KACA,QACA,KACA,MACA,MACA,MACA,KACA,KACA,KACA,UACA,OACA,MACA,KACA,MACA,SACA,QACA,OACA,MACA,KACA,OACA,SACA,OACA,OACA,QACA,MACA,OACA,MACA,MACA,MACA,MACA,OACA,KACA,MACA,OACA,MACA,MACA,MACA,UACA,IACA,KACA,KACA,OACA,KACA,KACA,MACA,OACA,QACA,MACA,OACA,SACA,MACA,KACA,QACA,OACA,OACA,KACA,UACA,KACA,MACA,MACA,KACA,MACA,QACA,KACA,OACA,KACA,QACA,MACA,MACA,SACA,OACA,MACA,OACA,MACA,SACA,QACA,KACA,OACA,OACA,OACA,MACA,QACA,OACA,OACA,QACA,QACA,OACA,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",
-  "names": []
-}
diff --git a/latest/assets/javascripts/workers/search.fe42c31b.min.js b/latest/assets/javascripts/workers/search.fe42c31b.min.js
new file mode 100644 (file)
index 0000000..65bea7c
--- /dev/null
@@ -0,0 +1,61 @@
+(()=>{var le=Object.create,U=Object.defineProperty,he=Object.getPrototypeOf,de=Object.prototype.hasOwnProperty,fe=Object.getOwnPropertyNames,pe=Object.getOwnPropertyDescriptor;var ge=t=>U(t,"__esModule",{value:!0});var q=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var ye=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of fe(e))!de.call(t,n)&&n!=="default"&&U(t,n,{get:()=>e[n],enumerable:!(r=pe(e,n))||r.enumerable});return t},Y=t=>ye(ge(U(t!=null?le(he(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var X=q((G,J)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i<n.length;i++){var s=n[i],o=e[s];if(Array.isArray(o)){r[s]=o.slice();continue}if(typeof o=="string"||typeof o=="number"||typeof o=="boolean"){r[s]=o;continue}throw new TypeError("clone is not deep and does not support nested objects")}return r},t.FieldRef=function(e,r,n){this.docRef=e,this.fieldName=r,this._stringValue=n},t.FieldRef.joiner="/",t.FieldRef.fromString=function(e){var r=e.indexOf(t.FieldRef.joiner);if(r===-1)throw"malformed field ref string";var n=e.slice(0,r),i=e.slice(r+1);return new t.FieldRef(i,n,e)},t.FieldRef.prototype.toString=function(){return this._stringValue==null&&(this._stringValue=this.fieldName+t.FieldRef.joiner+this.docRef),this._stringValue};t.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var r=0;r<this.length;r++)this.elements[e[r]]=!0}else this.length=0},t.Set.complete={intersect:function(e){return e},union:function(){return this},contains:function(){return!0}},t.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},t.Set.prototype.contains=function(e){return!!this.elements[e]},t.Set.prototype.intersect=function(e){var r,n,i,s=[];if(e===t.Set.complete)return this;if(e===t.Set.empty)return e;this.length<e.length?(r=this,n=e):(r=e,n=this),i=Object.keys(r.elements);for(var o=0;o<i.length;o++){var a=i[o];a in n.elements&&s.push(a)}return new t.Set(s)},t.Set.prototype.union=function(e){return e===t.Set.complete?t.Set.complete:e===t.Set.empty?this:new t.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},t.idf=function(e,r){var n=0;for(var i in e)i!="_index"&&(n+=Object.keys(e[i]).length);var s=(r-n+.5)/(n+.5);return Math.log(1+Math.abs(s))},t.Token=function(e,r){this.str=e||"",this.metadata=r||{}},t.Token.prototype.toString=function(){return this.str},t.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},t.Token.prototype.clone=function(e){return e=e||function(r){return r},new t.Token(e(this.str,this.metadata),this.metadata)};t.tokenizer=function(e,r){if(e==null||e==null)return[];if(Array.isArray(e))return e.map(function(y){return new t.Token(t.utils.asString(y).toLowerCase(),t.utils.clone(r))});for(var n=e.toString().toLowerCase(),i=n.length,s=[],o=0,a=0;o<=i;o++){var u=n.charAt(o),c=o-a;if(u.match(t.tokenizer.separator)||o==i){if(c>0){var d=t.utils.clone(r)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index.
+`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n<r;n++){for(var i=this._stack[n],s=[],o=0;o<e.length;o++){var a=i(e[o],o,e);if(!(a==null||a===""))if(Array.isArray(a))for(var u=0;u<a.length;u++)s.push(a[u]);else s.push(a)}e=s}return e},t.Pipeline.prototype.runString=function(e,r){var n=new t.Token(e,r);return this.run([n]).map(function(i){return i.toString()})},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})};t.Vector=function(e){this._magnitude=0,this.elements=e||[]},t.Vector.prototype.positionForIndex=function(e){if(this.elements.length==0)return 0;for(var r=0,n=this.elements.length/2,i=n-r,s=Math.floor(i/2),o=this.elements[s*2];i>1&&(o<e&&(r=s),o>e&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(o<e)return(s+1)*2},t.Vector.prototype.insert=function(e,r){this.upsert(e,r,function(){throw"duplicate index"})},t.Vector.prototype.upsert=function(e,r,n){this._magnitude=0;var i=this.positionForIndex(e);this.elements[i]==e?this.elements[i+1]=n(this.elements[i+1],r):this.elements.splice(i,0,e,r)},t.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,r=this.elements.length,n=1;n<r;n+=2){var i=this.elements[n];e+=i*i}return this._magnitude=Math.sqrt(e)},t.Vector.prototype.dot=function(e){for(var r=0,n=this.elements,i=e.elements,s=n.length,o=i.length,a=0,u=0,c=0,d=0;c<s&&d<o;)a=n[c],u=i[d],a<u?c+=2:a>u?d+=2:a==u&&(r+=n[c+1]*i[d+1],c+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r<this.elements.length;r+=2,n++)e[n]=this.elements[r];return e},t.Vector.prototype.toJSON=function(){return this.elements};t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},r={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",s=n+"[^aeiouy]*",o=i+"[aeiou]*",a="^("+s+")?"+o+s,u="^("+s+")?"+o+s+"("+o+")?$",c="^("+s+")?"+o+s+o+s,d="^("+s+")?"+i,y=new RegExp(a),p=new RegExp(c),b=new RegExp(u),m=new RegExp(d),Q=/^(.+?)(ss|i)es$/,f=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,L=/^(.+?)(ed|ing)$/,w=/.$/,k=/(at|bl|iz)$/,O=new RegExp("([^aeiouylsz])\\1$"),j=new RegExp("^"+s+i+"[^aeiouwxy]$"),C=/^(.+?[^aeiou])y$/,A=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,V=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,$=/^(.+?)(s|t)(ion)$/,P=/^(.+?)e$/,N=/ll$/,B=new RegExp("^"+s+i+"[^aeiouwxy]$"),M=function(l){var v,I,E,h,x,T,F;if(l.length<3)return l;if(E=l.substr(0,1),E=="y"&&(l=E.toUpperCase()+l.substr(1)),h=Q,x=f,h.test(l)?l=l.replace(h,"$1$2"):x.test(l)&&(l=l.replace(x,"$1$2")),h=g,x=L,h.test(l)){var S=h.exec(l);h=y,h.test(S[1])&&(h=w,l=l.replace(h,""))}else if(x.test(l)){var S=x.exec(l);v=S[1],x=m,x.test(v)&&(l=v,x=k,T=O,F=j,x.test(l)?l=l+"e":T.test(l)?(h=w,l=l.replace(h,"")):F.test(l)&&(l=l+"e"))}if(h=C,h.test(l)){var S=h.exec(l);v=S[1],l=v+"i"}if(h=A,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+e[I])}if(h=V,h.test(l)){var S=h.exec(l);v=S[1],I=S[2],h=y,h.test(v)&&(l=v+r[I])}if(h=D,x=$,h.test(l)){var S=h.exec(l);v=S[1],h=p,h.test(v)&&(l=v)}else if(x.test(l)){var S=x.exec(l);v=S[1]+S[2],x=p,x.test(v)&&(l=v)}if(h=P,h.test(l)){var S=h.exec(l);v=S[1],h=p,x=b,T=B,(h.test(v)||x.test(v)&&!T.test(v))&&(l=v)}return h=N,x=p,h.test(l)&&x.test(l)&&(h=w,l=l.replace(h,"")),E=="y"&&(l=E.toLowerCase()+l.substr(1)),l};return function(_){return _.update(M)}}(),t.Pipeline.registerFunction(t.stemmer,"stemmer");t.generateStopWordFilter=function(e){var r=e.reduce(function(n,i){return n[i]=i,n},{});return function(n){if(n&&r[n.toString()]!==n.toString())return n}},t.stopWordFilter=t.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter");t.trimmer=function(e){return e.update(function(r){return r.replace(/^\W+/,"").replace(/\W+$/,"")})},t.Pipeline.registerFunction(t.trimmer,"trimmer");t.TokenSet=function(){this.final=!1,this.edges={},this.id=t.TokenSet._nextId,t.TokenSet._nextId+=1},t.TokenSet._nextId=1,t.TokenSet.fromArray=function(e){for(var r=new t.TokenSet.Builder,n=0,i=e.length;n<i;n++)r.insert(e[n]);return r.finish(),r.root},t.TokenSet.fromClause=function(e){return"editDistance"in e?t.TokenSet.fromFuzzyString(e.term,e.editDistance):t.TokenSet.fromString(e.term)},t.TokenSet.fromFuzzyString=function(e,r){for(var n=new t.TokenSet,i=[{node:n,editsRemaining:r,str:e}];i.length;){var s=i.pop();if(s.str.length>0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),y=s.str.charAt(1),p;y in s.node.edges?p=s.node.edges[y]:(p=new t.TokenSet,s.node.edges[y]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i<s;i++){var o=e[i],a=i==s-1;if(o=="*")r.edges[o]=r,r.final=a;else{var u=new t.TokenSet;u.final=a,r.edges[o]=u,r=u}}return n},t.TokenSet.prototype.toArray=function(){for(var e=[],r=[{prefix:"",node:this}];r.length;){var n=r.pop(),i=Object.keys(n.node.edges),s=i.length;n.node.final&&(n.prefix.charAt(0),e.push(n.prefix));for(var o=0;o<s;o++){var a=i[o];r.push({prefix:n.prefix.concat(a),node:n.node.edges[a]})}}return e},t.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",r=Object.keys(this.edges).sort(),n=r.length,i=0;i<n;i++){var s=r[i],o=this.edges[s];e=e+s+o.id}return e},t.TokenSet.prototype.intersect=function(e){for(var r=new t.TokenSet,n=void 0,i=[{qNode:e,output:r,node:this}];i.length;){n=i.pop();for(var s=Object.keys(n.qNode.edges),o=s.length,a=Object.keys(n.node.edges),u=a.length,c=0;c<o;c++)for(var d=s[c],y=0;y<u;y++){var p=a[y];if(p==d||d=="*"){var b=n.node.edges[p],m=n.qNode.edges[d],Q=b.final&&m.final,f=void 0;p in n.output.edges?(f=n.output.edges[p],f.final=f.final||Q):(f=new t.TokenSet,f.final=Q,n.output.edges[p]=f),i.push({qNode:m,output:f,node:b})}}}return r},t.TokenSet.Builder=function(){this.previousWord="",this.root=new t.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},t.TokenSet.Builder.prototype.insert=function(e){var r,n=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var i=0;i<e.length&&i<this.previousWord.length&&e[i]==this.previousWord[i];i++)n++;this.minimize(n),this.uncheckedNodes.length==0?r=this.root:r=this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(var i=n;i<e.length;i++){var s=new t.TokenSet,o=e[i];r.edges[o]=s,this.uncheckedNodes.push({parent:r,char:o,child:s}),r=s}r.final=!0,this.previousWord=e},t.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},t.TokenSet.Builder.prototype.minimize=function(e){for(var r=this.uncheckedNodes.length-1;r>=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u<this.fields.length;u++)i[this.fields[u]]=new t.Vector;e.call(r,r);for(var u=0;u<r.clauses.length;u++){var c=r.clauses[u],d=null,y=t.Set.empty;c.usePipeline?d=this.pipeline.runString(c.term,{fields:c.fields}):d=[c.term];for(var p=0;p<d.length;p++){var b=d[p];c.term=b;var m=t.TokenSet.fromClause(c),Q=this.tokenSet.intersect(m).toArray();if(Q.length===0&&c.presence===t.Query.presence.REQUIRED){for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=t.Set.empty}break}for(var L=0;L<Q.length;L++)for(var w=Q[L],k=this.invertedIndex[w],O=k._index,f=0;f<c.fields.length;f++){var g=c.fields[f],j=k[g],C=Object.keys(j),A=w+"/"+g,V=new t.Set(C);if(c.presence==t.Query.presence.REQUIRED&&(y=y.union(V),o[g]===void 0&&(o[g]=t.Set.complete)),c.presence==t.Query.presence.PROHIBITED){a[g]===void 0&&(a[g]=t.Set.empty),a[g]=a[g].union(V);continue}if(i[g].upsert(O,c.boost,function(ue,ce){return ue+ce}),!s[A]){for(var D=0;D<C.length;D++){var $=C[D],P=new t.FieldRef($,g),N=j[$],B;(B=n[P])===void 0?n[P]=new t.MatchData(w,g,N):B.add(w,g,N)}s[A]=!0}}}if(c.presence===t.Query.presence.REQUIRED)for(var f=0;f<c.fields.length;f++){var g=c.fields[f];o[g]=o[g].intersect(y)}}for(var M=t.Set.complete,_=t.Set.empty,u=0;u<this.fields.length;u++){var g=this.fields[u];o[g]&&(M=M.intersect(o[g])),a[g]&&(_=_.union(a[g]))}var l=Object.keys(n),v=[],I=Object.create(null);if(r.isNegated()){l=Object.keys(this.fieldVectors);for(var u=0;u<l.length;u++){var P=l[u],E=t.FieldRef.fromString(P);n[P]=new t.MatchData}}for(var u=0;u<l.length;u++){var E=t.FieldRef.fromString(l[u]),h=E.docRef;if(!!M.contains(h)&&!_.contains(h)){var x=this.fieldVectors[E],T=i[E.fieldName].similarity(x),F;if((F=I[h])!==void 0)F.score+=T,F.matchData.combine(n[E]);else{var S={ref:h,score:T,matchData:n[E]};I[h]=S,v.push(S)}}}return v.sort(function(oe,ae){return ae.score-oe.score})},t.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map(function(n){return[n,this.invertedIndex[n]]},this),r=Object.keys(this.fieldVectors).map(function(n){return[n,this.fieldVectors[n].toJSON()]},this);return{version:t.version,fields:this.fields,fieldVectors:r,invertedIndex:e,pipeline:this.pipeline.toJSON()}},t.Index.load=function(e){var r={},n={},i=e.fieldVectors,s=Object.create(null),o=e.invertedIndex,a=new t.TokenSet.Builder,u=t.Pipeline.load(e.pipeline);e.version!=t.version&&t.utils.warn("Version mismatch when loading serialised index. Current version of lunr '"+t.version+"' does not match serialized index '"+e.version+"'");for(var c=0;c<i.length;c++){var d=i[c],y=d[0],p=d[1];n[y]=new t.Vector(p)}for(var c=0;c<o.length;c++){var d=o[c],b=d[0],m=d[1];a.insert(b),s[b]=m}return a.finish(),r.fields=e.fields,r.fieldVectors=n,r.invertedIndex=s,r.tokenSet=a.root,r.pipeline=u,new t.Index(r)};t.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=t.tokenizer,this.pipeline=new t.Pipeline,this.searchPipeline=new t.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},t.Builder.prototype.ref=function(e){this._ref=e},t.Builder.prototype.field=function(e,r){if(/\//.test(e))throw new RangeError("Field '"+e+"' contains illegal character '/'");this._fields[e]=r||{}},t.Builder.prototype.b=function(e){e<0?this._b=0:e>1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s<i.length;s++){var o=i[s],a=this._fields[o].extractor,u=a?a(e):e[o],c=this.tokenizer(u,{fields:[o]}),d=this.pipeline.run(c),y=new t.FieldRef(n,o),p=Object.create(null);this.fieldTermFrequencies[y]=p,this.fieldLengths[y]=0,this.fieldLengths[y]+=d.length;for(var b=0;b<d.length;b++){var m=d[b];if(p[m]==null&&(p[m]=0),p[m]+=1,this.invertedIndex[m]==null){var Q=Object.create(null);Q._index=this.termIndex,this.termIndex+=1;for(var f=0;f<i.length;f++)Q[i[f]]=Object.create(null);this.invertedIndex[m]=Q}this.invertedIndex[m][o][n]==null&&(this.invertedIndex[m][o][n]=Object.create(null));for(var g=0;g<this.metadataWhitelist.length;g++){var L=this.metadataWhitelist[g],w=m.metadata[L];this.invertedIndex[m][o][n][L]==null&&(this.invertedIndex[m][o][n][L]=[]),this.invertedIndex[m][o][n][L].push(w)}}}},t.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),r=e.length,n={},i={},s=0;s<r;s++){var o=t.FieldRef.fromString(e[s]),a=o.fieldName;i[a]||(i[a]=0),i[a]+=1,n[a]||(n[a]=0),n[a]+=this.fieldLengths[o]}for(var u=Object.keys(this._fields),s=0;s<u.length;s++){var c=u[s];n[c]=n[c]/i[c]}this.averageFieldLength=n},t.Builder.prototype.createFieldVectors=function(){for(var e={},r=Object.keys(this.fieldTermFrequencies),n=r.length,i=Object.create(null),s=0;s<n;s++){for(var o=t.FieldRef.fromString(r[s]),a=o.fieldName,u=this.fieldLengths[o],c=new t.Vector,d=this.fieldTermFrequencies[o],y=Object.keys(d),p=y.length,b=this._fields[a].boost||1,m=this._documents[o.docRef].boost||1,Q=0;Q<p;Q++){var f=y[Q],g=d[f],L=this.invertedIndex[f]._index,w,k,O;i[f]===void 0?(w=t.idf(this.invertedIndex[f],this.documentCount),i[f]=w):w=i[f],k=w*((this._k1+1)*g)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[a]))+g),k*=b,k*=m,O=Math.round(k*1e3)/1e3,c.insert(L,O)}e[o]=c}this.fieldVectors=e},t.Builder.prototype.createTokenSet=function(){this.tokenSet=t.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},t.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new t.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},t.Builder.prototype.use=function(e){var r=Array.prototype.slice.call(arguments,1);r.unshift(this),e.apply(this,r)},t.MatchData=function(e,r,n){for(var i=Object.create(null),s=Object.keys(n||{}),o=0;o<s.length;o++){var a=s[o];i[a]=n[a].slice()}this.metadata=Object.create(null),e!==void 0&&(this.metadata[e]=Object.create(null),this.metadata[e][r]=i)},t.MatchData.prototype.combine=function(e){for(var r=Object.keys(e.metadata),n=0;n<r.length;n++){var i=r[n],s=Object.keys(e.metadata[i]);this.metadata[i]==null&&(this.metadata[i]=Object.create(null));for(var o=0;o<s.length;o++){var a=s[o],u=Object.keys(e.metadata[i][a]);this.metadata[i][a]==null&&(this.metadata[i][a]=Object.create(null));for(var c=0;c<u.length;c++){var d=u[c];this.metadata[i][a][d]==null?this.metadata[i][a][d]=e.metadata[i][a][d]:this.metadata[i][a][d]=this.metadata[i][a][d].concat(e.metadata[i][a][d])}}}},t.MatchData.prototype.add=function(e,r,n){if(!(e in this.metadata)){this.metadata[e]=Object.create(null),this.metadata[e][r]=n;return}if(!(r in this.metadata[e])){this.metadata[e][r]=n;return}for(var i=Object.keys(n),s=0;s<i.length;s++){var o=i[s];o in this.metadata[e][r]?this.metadata[e][r][o]=this.metadata[e][r][o].concat(n[o]):this.metadata[e][r][o]=n[o]}},t.Query=function(e){this.clauses=[],this.allFields=e},t.Query.wildcard=new String("*"),t.Query.wildcard.NONE=0,t.Query.wildcard.LEADING=1,t.Query.wildcard.TRAILING=2,t.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},t.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=t.Query.wildcard.NONE),e.wildcard&t.Query.wildcard.LEADING&&e.term.charAt(0)!=t.Query.wildcard&&(e.term="*"+e.term),e.wildcard&t.Query.wildcard.TRAILING&&e.term.slice(-1)!=t.Query.wildcard&&(e.term=""+e.term+"*"),"presence"in e||(e.presence=t.Query.presence.OPTIONAL),this.clauses.push(e),this},t.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=t.Query.presence.PROHIBITED)return!1;return!0},t.Query.prototype.term=function(e,r){if(Array.isArray(e))return e.forEach(function(i){this.term(i,t.utils.clone(r))},this),this;var n=r||{};return n.term=e.toString(),this.clause(n),this},t.QueryParseError=function(e,r,n){this.name="QueryParseError",this.message=e,this.start=r,this.end=n},t.QueryParseError.prototype=new Error,t.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},t.QueryLexer.prototype.run=function(){for(var e=t.QueryLexer.lexText;e;)e=e(this)},t.QueryLexer.prototype.sliceString=function(){for(var e=[],r=this.start,n=this.pos,i=0;i<this.escapeCharPositions.length;i++)n=this.escapeCharPositions[i],e.push(this.str.slice(r,n)),r=n+1;return e.push(this.str.slice(r,this.pos)),this.escapeCharPositions.length=0,e.join("")},t.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},t.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},t.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos<this.length},t.QueryLexer.EOS="EOS",t.QueryLexer.FIELD="FIELD",t.QueryLexer.TERM="TERM",t.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",t.QueryLexer.BOOST="BOOST",t.QueryLexer.PRESENCE="PRESENCE",t.QueryLexer.lexField=function(e){return e.backup(),e.emit(t.QueryLexer.FIELD),e.ignore(),t.QueryLexer.lexText},t.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof G=="object"?J.exports=r():e.lunr=r()}(this,function(){return t})})()});var K=q((we,Z)=>{"use strict";var me=/["'&<>]/;Z.exports=ve;function ve(t){var e=""+t,r=me.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s<e.length;s++){switch(e.charCodeAt(s)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 39:n="&#39;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}o!==s&&(i+=e.substring(o,s)),o=s+1,i+=n}return o!==s?i+e.substring(o,s):i}});var se=Y(X());var ee=Y(K());function te(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=(0,ee.default)(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let c=e.get(i);r.has(c)?e.set(o,{location:o,title:a,text:u,parent:c}):(c.title=n.title,c.text=u,r.add(c))}else e.set(o,{location:o,title:a,text:u})}return e}function re(t){let e=new RegExp(t.separator,"img"),r=(n,i,s)=>`${i}<mark data-md-highlight>${s}</mark>`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${t.separator})(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(e,"|")})`,"img");return s=>s.replace(i,r).replace(/<\/mark>(\s+)<mark[^>]*>/img,"$1")}}function ne(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ie(t,e){let r=new Set(t),n={};for(let i=0;i<e.length;i++)for(let s of r)e[i].startsWith(s.term)&&(n[s.term]=!0,r.delete(s));for(let i of r)n[i.term]=!1;return n}function xe(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var W=class{constructor({config:e,docs:r,pipeline:n,index:i}){this.documents=te(r),this.highlight=re(e),lunr.tokenizer.separator=new RegExp(e.separator),typeof i=="undefined"?this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let s=xe(["trimmer","stopWordFilter","stemmer"],n);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.field("title",{boost:1e3}),this.field("text"),this.ref("location");for(let o of r)this.add(o)}):this.index=lunr.Index.load(i)}search(e){if(e)try{let r=this.highlight(e),n=ne(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED);return[...this.index.search(`${e}*`).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.documents.get(o);if(typeof c!="undefined"){let{location:d,title:y,text:p,parent:b}=c,m=ie(n,Object.keys(u.metadata)),Q=+!b+ +Object.values(m).every(f=>f);s.push({location:d,title:r(y),text:r(p),score:a*(1+Q),terms:m})}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.documents.get(o.location);if(typeof a!="undefined"){let u="parent"in a?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map).values()]}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return[]}};var R;(function(t){t[t.SETUP=0]="SETUP",t[t.READY=1]="READY",t[t.QUERY=2]="QUERY",t[t.RESULT=3]="RESULT"})(R||(R={}));var H;function Se(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang)n==="ja"&&r.push(`${e}/tinyseg.js`),n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`);t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Qe(t){return z(this,null,function*(){switch(t.type){case R.SETUP:return yield Se(t.data.config),H=new W(t.data),{type:R.READY};case R.QUERY:return{type:R.RESULT,data:H?H.search(t.data):[]};default:throw new TypeError("Invalid message type")}})}self.lunr=se.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Qe(t.data))}));})();
+/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */
+/*!
+ * lunr.Builder
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Index
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Pipeline
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Set
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.TokenSet
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.Vector
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.stemmer
+ * Copyright (C) 2020 Oliver Nightingale
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
+ */
+/*!
+ * lunr.stopWordFilter
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.tokenizer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.trimmer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/*!
+ * lunr.utils
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+/**
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
+ * Copyright (C) 2020 Oliver Nightingale
+ * @license MIT
+ */
+//# sourceMappingURL=search.fe42c31b.min.js.map
+
diff --git a/latest/assets/javascripts/workers/search.fe42c31b.min.js.map b/latest/assets/javascripts/workers/search.fe42c31b.min.js.map
new file mode 100644 (file)
index 0000000..3c9fcb6
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "version": 3,
+  "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts"],
+  "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n *   this.field('title')\n *   this.field('body')\n *   this.ref('id')\n *\n *   documents.forEach(function (doc) {\n *     this.add(doc)\n *   }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n  var builder = new lunr.Builder\n\n  builder.pipeline.add(\n    lunr.trimmer,\n    lunr.stopWordFilter,\n    lunr.stemmer\n  )\n\n  builder.searchPipeline.add(\n    lunr.stemmer\n  )\n\n  config.call(builder, builder)\n  return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n  /* eslint-disable no-console */\n  return function (message) {\n    if (global.console && console.warn) {\n      console.warn(message)\n    }\n  }\n  /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n  if (obj === void 0 || obj === null) {\n    return \"\"\n  } else {\n    return obj.toString()\n  }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n  if (obj === null || obj === undefined) {\n    return obj\n  }\n\n  var clone = Object.create(null),\n      keys = Object.keys(obj)\n\n  for (var i = 0; i < keys.length; i++) {\n    var key = keys[i],\n        val = obj[key]\n\n    if (Array.isArray(val)) {\n      clone[key] = val.slice()\n      continue\n    }\n\n    if (typeof val === 'string' ||\n        typeof val === 'number' ||\n        typeof val === 'boolean') {\n      clone[key] = val\n      continue\n    }\n\n    throw new TypeError(\"clone is not deep and does not support nested objects\")\n  }\n\n  return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n  this.docRef = docRef\n  this.fieldName = fieldName\n  this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n  var n = s.indexOf(lunr.FieldRef.joiner)\n\n  if (n === -1) {\n    throw \"malformed field ref string\"\n  }\n\n  var fieldRef = s.slice(0, n),\n      docRef = s.slice(n + 1)\n\n  return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n  if (this._stringValue == undefined) {\n    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n  }\n\n  return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n  this.elements = Object.create(null)\n\n  if (elements) {\n    this.length = elements.length\n\n    for (var i = 0; i < this.length; i++) {\n      this.elements[elements[i]] = true\n    }\n  } else {\n    this.length = 0\n  }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n  intersect: function (other) {\n    return other\n  },\n\n  union: function () {\n    return this\n  },\n\n  contains: function () {\n    return true\n  }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n  intersect: function () {\n    return this\n  },\n\n  union: function (other) {\n    return other\n  },\n\n  contains: function () {\n    return false\n  }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n  return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n  var a, b, elements, intersection = []\n\n  if (other === lunr.Set.complete) {\n    return this\n  }\n\n  if (other === lunr.Set.empty) {\n    return other\n  }\n\n  if (this.length < other.length) {\n    a = this\n    b = other\n  } else {\n    a = other\n    b = this\n  }\n\n  elements = Object.keys(a.elements)\n\n  for (var i = 0; i < elements.length; i++) {\n    var element = elements[i]\n    if (element in b.elements) {\n      intersection.push(element)\n    }\n  }\n\n  return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n  if (other === lunr.Set.complete) {\n    return lunr.Set.complete\n  }\n\n  if (other === lunr.Set.empty) {\n    return this\n  }\n\n  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n  var documentsWithTerm = 0\n\n  for (var fieldName in posting) {\n    if (fieldName == '_index') continue // Ignore the term index, its not a field\n    documentsWithTerm += Object.keys(posting[fieldName]).length\n  }\n\n  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n  return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n  this.str = str || \"\"\n  this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n  return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n *   return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n  this.str = fn(this.str, this.metadata)\n  return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n  fn = fn || function (s) { return s }\n  return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n  if (obj == null || obj == undefined) {\n    return []\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.map(function (t) {\n      return new lunr.Token(\n        lunr.utils.asString(t).toLowerCase(),\n        lunr.utils.clone(metadata)\n      )\n    })\n  }\n\n  var str = obj.toString().toLowerCase(),\n      len = str.length,\n      tokens = []\n\n  for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n    var char = str.charAt(sliceEnd),\n        sliceLength = sliceEnd - sliceStart\n\n    if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n      if (sliceLength > 0) {\n        var tokenMetadata = lunr.utils.clone(metadata) || {}\n        tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n        tokenMetadata[\"index\"] = tokens.length\n\n        tokens.push(\n          new lunr.Token (\n            str.slice(sliceStart, sliceEnd),\n            tokenMetadata\n          )\n        )\n      }\n\n      sliceStart = sliceEnd + 1\n    }\n\n  }\n\n  return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n  this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n  if (label in this.registeredFunctions) {\n    lunr.utils.warn('Overwriting existing registered function: ' + label)\n  }\n\n  fn.label = label\n  lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n  var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n  if (!isRegistered) {\n    lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n  }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n  var pipeline = new lunr.Pipeline\n\n  serialised.forEach(function (fnName) {\n    var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n    if (fn) {\n      pipeline.add(fn)\n    } else {\n      throw new Error('Cannot load unregistered function: ' + fnName)\n    }\n  })\n\n  return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n  var fns = Array.prototype.slice.call(arguments)\n\n  fns.forEach(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n    this._stack.push(fn)\n  }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  pos = pos + 1\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n  var pos = this._stack.indexOf(existingFn)\n  if (pos == -1) {\n    throw new Error('Cannot find existingFn')\n  }\n\n  this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n  var pos = this._stack.indexOf(fn)\n  if (pos == -1) {\n    return\n  }\n\n  this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n  var stackLength = this._stack.length\n\n  for (var i = 0; i < stackLength; i++) {\n    var fn = this._stack[i]\n    var memo = []\n\n    for (var j = 0; j < tokens.length; j++) {\n      var result = fn(tokens[j], j, tokens)\n\n      if (result === null || result === void 0 || result === '') continue\n\n      if (Array.isArray(result)) {\n        for (var k = 0; k < result.length; k++) {\n          memo.push(result[k])\n        }\n      } else {\n        memo.push(result)\n      }\n    }\n\n    tokens = memo\n  }\n\n  return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n  var token = new lunr.Token (str, metadata)\n\n  return this.run([token]).map(function (t) {\n    return t.toString()\n  })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n  this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n  return this._stack.map(function (fn) {\n    lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n    return fn.label\n  })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n  this._magnitude = 0\n  this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n  // For an empty vector the tuple can be inserted at the beginning\n  if (this.elements.length == 0) {\n    return 0\n  }\n\n  var start = 0,\n      end = this.elements.length / 2,\n      sliceLength = end - start,\n      pivotPoint = Math.floor(sliceLength / 2),\n      pivotIndex = this.elements[pivotPoint * 2]\n\n  while (sliceLength > 1) {\n    if (pivotIndex < index) {\n      start = pivotPoint\n    }\n\n    if (pivotIndex > index) {\n      end = pivotPoint\n    }\n\n    if (pivotIndex == index) {\n      break\n    }\n\n    sliceLength = end - start\n    pivotPoint = start + Math.floor(sliceLength / 2)\n    pivotIndex = this.elements[pivotPoint * 2]\n  }\n\n  if (pivotIndex == index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex > index) {\n    return pivotPoint * 2\n  }\n\n  if (pivotIndex < index) {\n    return (pivotPoint + 1) * 2\n  }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n  this.upsert(insertIdx, val, function () {\n    throw \"duplicate index\"\n  })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n  this._magnitude = 0\n  var position = this.positionForIndex(insertIdx)\n\n  if (this.elements[position] == insertIdx) {\n    this.elements[position + 1] = fn(this.elements[position + 1], val)\n  } else {\n    this.elements.splice(position, 0, insertIdx, val)\n  }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n  if (this._magnitude) return this._magnitude\n\n  var sumOfSquares = 0,\n      elementsLength = this.elements.length\n\n  for (var i = 1; i < elementsLength; i += 2) {\n    var val = this.elements[i]\n    sumOfSquares += val * val\n  }\n\n  return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n  var dotProduct = 0,\n      a = this.elements, b = otherVector.elements,\n      aLen = a.length, bLen = b.length,\n      aVal = 0, bVal = 0,\n      i = 0, j = 0\n\n  while (i < aLen && j < bLen) {\n    aVal = a[i], bVal = b[j]\n    if (aVal < bVal) {\n      i += 2\n    } else if (aVal > bVal) {\n      j += 2\n    } else if (aVal == bVal) {\n      dotProduct += a[i + 1] * b[j + 1]\n      i += 2\n      j += 2\n    }\n  }\n\n  return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n  return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n  var output = new Array (this.elements.length / 2)\n\n  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n    output[j] = this.elements[i]\n  }\n\n  return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n  return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n  var step2list = {\n      \"ational\" : \"ate\",\n      \"tional\" : \"tion\",\n      \"enci\" : \"ence\",\n      \"anci\" : \"ance\",\n      \"izer\" : \"ize\",\n      \"bli\" : \"ble\",\n      \"alli\" : \"al\",\n      \"entli\" : \"ent\",\n      \"eli\" : \"e\",\n      \"ousli\" : \"ous\",\n      \"ization\" : \"ize\",\n      \"ation\" : \"ate\",\n      \"ator\" : \"ate\",\n      \"alism\" : \"al\",\n      \"iveness\" : \"ive\",\n      \"fulness\" : \"ful\",\n      \"ousness\" : \"ous\",\n      \"aliti\" : \"al\",\n      \"iviti\" : \"ive\",\n      \"biliti\" : \"ble\",\n      \"logi\" : \"log\"\n    },\n\n    step3list = {\n      \"icate\" : \"ic\",\n      \"ative\" : \"\",\n      \"alize\" : \"al\",\n      \"iciti\" : \"ic\",\n      \"ical\" : \"ic\",\n      \"ful\" : \"\",\n      \"ness\" : \"\"\n    },\n\n    c = \"[^aeiou]\",          // consonant\n    v = \"[aeiouy]\",          // vowel\n    C = c + \"[^aeiouy]*\",    // consonant sequence\n    V = v + \"[aeiou]*\",      // vowel sequence\n\n    mgr0 = \"^(\" + C + \")?\" + V + C,               // [C]VC... is m>0\n    meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\",  // [C]VC[V] is m=1\n    mgr1 = \"^(\" + C + \")?\" + V + C + V + C,       // [C]VCVC... is m>1\n    s_v = \"^(\" + C + \")?\" + v;                   // vowel in stem\n\n  var re_mgr0 = new RegExp(mgr0);\n  var re_mgr1 = new RegExp(mgr1);\n  var re_meq1 = new RegExp(meq1);\n  var re_s_v = new RegExp(s_v);\n\n  var re_1a = /^(.+?)(ss|i)es$/;\n  var re2_1a = /^(.+?)([^s])s$/;\n  var re_1b = /^(.+?)eed$/;\n  var re2_1b = /^(.+?)(ed|ing)$/;\n  var re_1b_2 = /.$/;\n  var re2_1b_2 = /(at|bl|iz)$/;\n  var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n  var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var re_1c = /^(.+?[^aeiou])y$/;\n  var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n  var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n  var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n  var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n  var re_5 = /^(.+?)e$/;\n  var re_5_1 = /ll$/;\n  var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n  var porterStemmer = function porterStemmer(w) {\n    var stem,\n      suffix,\n      firstch,\n      re,\n      re2,\n      re3,\n      re4;\n\n    if (w.length < 3) { return w; }\n\n    firstch = w.substr(0,1);\n    if (firstch == \"y\") {\n      w = firstch.toUpperCase() + w.substr(1);\n    }\n\n    // Step 1a\n    re = re_1a\n    re2 = re2_1a;\n\n    if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n    else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n    // Step 1b\n    re = re_1b;\n    re2 = re2_1b;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      re = re_mgr0;\n      if (re.test(fp[1])) {\n        re = re_1b_2;\n        w = w.replace(re,\"\");\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1];\n      re2 = re_s_v;\n      if (re2.test(stem)) {\n        w = stem;\n        re2 = re2_1b_2;\n        re3 = re3_1b_2;\n        re4 = re4_1b_2;\n        if (re2.test(w)) { w = w + \"e\"; }\n        else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n        else if (re4.test(w)) { w = w + \"e\"; }\n      }\n    }\n\n    // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n    re = re_1c;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      w = stem + \"i\";\n    }\n\n    // Step 2\n    re = re_2;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step2list[suffix];\n      }\n    }\n\n    // Step 3\n    re = re_3;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      suffix = fp[2];\n      re = re_mgr0;\n      if (re.test(stem)) {\n        w = stem + step3list[suffix];\n      }\n    }\n\n    // Step 4\n    re = re_4;\n    re2 = re2_4;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      if (re.test(stem)) {\n        w = stem;\n      }\n    } else if (re2.test(w)) {\n      var fp = re2.exec(w);\n      stem = fp[1] + fp[2];\n      re2 = re_mgr1;\n      if (re2.test(stem)) {\n        w = stem;\n      }\n    }\n\n    // Step 5\n    re = re_5;\n    if (re.test(w)) {\n      var fp = re.exec(w);\n      stem = fp[1];\n      re = re_mgr1;\n      re2 = re_meq1;\n      re3 = re3_5;\n      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n        w = stem;\n      }\n    }\n\n    re = re_5_1;\n    re2 = re_mgr1;\n    if (re.test(w) && re2.test(w)) {\n      re = re_1b_2;\n      w = w.replace(re,\"\");\n    }\n\n    // and turn initial Y back to y\n\n    if (firstch == \"y\") {\n      w = firstch.toLowerCase() + w.substr(1);\n    }\n\n    return w;\n  };\n\n  return function (token) {\n    return token.update(porterStemmer);\n  }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n  var words = stopWords.reduce(function (memo, stopWord) {\n    memo[stopWord] = stopWord\n    return memo\n  }, {})\n\n  return function (token) {\n    if (token && words[token.toString()] !== token.toString()) return token\n  }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n  'a',\n  'able',\n  'about',\n  'across',\n  'after',\n  'all',\n  'almost',\n  'also',\n  'am',\n  'among',\n  'an',\n  'and',\n  'any',\n  'are',\n  'as',\n  'at',\n  'be',\n  'because',\n  'been',\n  'but',\n  'by',\n  'can',\n  'cannot',\n  'could',\n  'dear',\n  'did',\n  'do',\n  'does',\n  'either',\n  'else',\n  'ever',\n  'every',\n  'for',\n  'from',\n  'get',\n  'got',\n  'had',\n  'has',\n  'have',\n  'he',\n  'her',\n  'hers',\n  'him',\n  'his',\n  'how',\n  'however',\n  'i',\n  'if',\n  'in',\n  'into',\n  'is',\n  'it',\n  'its',\n  'just',\n  'least',\n  'let',\n  'like',\n  'likely',\n  'may',\n  'me',\n  'might',\n  'most',\n  'must',\n  'my',\n  'neither',\n  'no',\n  'nor',\n  'not',\n  'of',\n  'off',\n  'often',\n  'on',\n  'only',\n  'or',\n  'other',\n  'our',\n  'own',\n  'rather',\n  'said',\n  'say',\n  'says',\n  'she',\n  'should',\n  'since',\n  'so',\n  'some',\n  'than',\n  'that',\n  'the',\n  'their',\n  'them',\n  'then',\n  'there',\n  'these',\n  'they',\n  'this',\n  'tis',\n  'to',\n  'too',\n  'twas',\n  'us',\n  'wants',\n  'was',\n  'we',\n  'were',\n  'what',\n  'when',\n  'where',\n  'which',\n  'while',\n  'who',\n  'whom',\n  'why',\n  'will',\n  'with',\n  'would',\n  'yet',\n  'you',\n  'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n  return token.update(function (s) {\n    return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n  })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n  this.final = false\n  this.edges = {}\n  this.id = lunr.TokenSet._nextId\n  lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n  var builder = new lunr.TokenSet.Builder\n\n  for (var i = 0, len = arr.length; i < len; i++) {\n    builder.insert(arr[i])\n  }\n\n  builder.finish()\n  return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n  if ('editDistance' in clause) {\n    return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n  } else {\n    return lunr.TokenSet.fromString(clause.term)\n  }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n  var root = new lunr.TokenSet\n\n  var stack = [{\n    node: root,\n    editsRemaining: editDistance,\n    str: str\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop()\n\n    // no edit\n    if (frame.str.length > 0) {\n      var char = frame.str.charAt(0),\n          noEditNode\n\n      if (char in frame.node.edges) {\n        noEditNode = frame.node.edges[char]\n      } else {\n        noEditNode = new lunr.TokenSet\n        frame.node.edges[char] = noEditNode\n      }\n\n      if (frame.str.length == 1) {\n        noEditNode.final = true\n      }\n\n      stack.push({\n        node: noEditNode,\n        editsRemaining: frame.editsRemaining,\n        str: frame.str.slice(1)\n      })\n    }\n\n    if (frame.editsRemaining == 0) {\n      continue\n    }\n\n    // insertion\n    if (\"*\" in frame.node.edges) {\n      var insertionNode = frame.node.edges[\"*\"]\n    } else {\n      var insertionNode = new lunr.TokenSet\n      frame.node.edges[\"*\"] = insertionNode\n    }\n\n    if (frame.str.length == 0) {\n      insertionNode.final = true\n    }\n\n    stack.push({\n      node: insertionNode,\n      editsRemaining: frame.editsRemaining - 1,\n      str: frame.str\n    })\n\n    // deletion\n    // can only do a deletion if we have enough edits remaining\n    // and if there are characters left to delete in the string\n    if (frame.str.length > 1) {\n      stack.push({\n        node: frame.node,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // deletion\n    // just removing the last character from the str\n    if (frame.str.length == 1) {\n      frame.node.final = true\n    }\n\n    // substitution\n    // can only do a substitution if we have enough edits remaining\n    // and if there are characters left to substitute\n    if (frame.str.length >= 1) {\n      if (\"*\" in frame.node.edges) {\n        var substitutionNode = frame.node.edges[\"*\"]\n      } else {\n        var substitutionNode = new lunr.TokenSet\n        frame.node.edges[\"*\"] = substitutionNode\n      }\n\n      if (frame.str.length == 1) {\n        substitutionNode.final = true\n      }\n\n      stack.push({\n        node: substitutionNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: frame.str.slice(1)\n      })\n    }\n\n    // transposition\n    // can only do a transposition if there are edits remaining\n    // and there are enough characters to transpose\n    if (frame.str.length > 1) {\n      var charA = frame.str.charAt(0),\n          charB = frame.str.charAt(1),\n          transposeNode\n\n      if (charB in frame.node.edges) {\n        transposeNode = frame.node.edges[charB]\n      } else {\n        transposeNode = new lunr.TokenSet\n        frame.node.edges[charB] = transposeNode\n      }\n\n      if (frame.str.length == 1) {\n        transposeNode.final = true\n      }\n\n      stack.push({\n        node: transposeNode,\n        editsRemaining: frame.editsRemaining - 1,\n        str: charA + frame.str.slice(2)\n      })\n    }\n  }\n\n  return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n  var node = new lunr.TokenSet,\n      root = node\n\n  /*\n   * Iterates through all characters within the passed string\n   * appending a node for each character.\n   *\n   * When a wildcard character is found then a self\n   * referencing edge is introduced to continually match\n   * any number of any characters.\n   */\n  for (var i = 0, len = str.length; i < len; i++) {\n    var char = str[i],\n        final = (i == len - 1)\n\n    if (char == \"*\") {\n      node.edges[char] = node\n      node.final = final\n\n    } else {\n      var next = new lunr.TokenSet\n      next.final = final\n\n      node.edges[char] = next\n      node = next\n    }\n  }\n\n  return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n  var words = []\n\n  var stack = [{\n    prefix: \"\",\n    node: this\n  }]\n\n  while (stack.length) {\n    var frame = stack.pop(),\n        edges = Object.keys(frame.node.edges),\n        len = edges.length\n\n    if (frame.node.final) {\n      /* In Safari, at this point the prefix is sometimes corrupted, see:\n       * https://github.com/olivernn/lunr.js/issues/279 Calling any\n       * String.prototype method forces Safari to \"cast\" this string to what\n       * it's supposed to be, fixing the bug. */\n      frame.prefix.charAt(0)\n      words.push(frame.prefix)\n    }\n\n    for (var i = 0; i < len; i++) {\n      var edge = edges[i]\n\n      stack.push({\n        prefix: frame.prefix.concat(edge),\n        node: frame.node.edges[edge]\n      })\n    }\n  }\n\n  return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n  // NOTE: Using Object.keys here as this.edges is very likely\n  // to enter 'hash-mode' with many keys being added\n  //\n  // avoiding a for-in loop here as it leads to the function\n  // being de-optimised (at least in V8). From some simple\n  // benchmarks the performance is comparable, but allowing\n  // V8 to optimize may mean easy performance wins in the future.\n\n  if (this._str) {\n    return this._str\n  }\n\n  var str = this.final ? '1' : '0',\n      labels = Object.keys(this.edges).sort(),\n      len = labels.length\n\n  for (var i = 0; i < len; i++) {\n    var label = labels[i],\n        node = this.edges[label]\n\n    str = str + label + node.id\n  }\n\n  return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n  var output = new lunr.TokenSet,\n      frame = undefined\n\n  var stack = [{\n    qNode: b,\n    output: output,\n    node: this\n  }]\n\n  while (stack.length) {\n    frame = stack.pop()\n\n    // NOTE: As with the #toString method, we are using\n    // Object.keys and a for loop instead of a for-in loop\n    // as both of these objects enter 'hash' mode, causing\n    // the function to be de-optimised in V8\n    var qEdges = Object.keys(frame.qNode.edges),\n        qLen = qEdges.length,\n        nEdges = Object.keys(frame.node.edges),\n        nLen = nEdges.length\n\n    for (var q = 0; q < qLen; q++) {\n      var qEdge = qEdges[q]\n\n      for (var n = 0; n < nLen; n++) {\n        var nEdge = nEdges[n]\n\n        if (nEdge == qEdge || qEdge == '*') {\n          var node = frame.node.edges[nEdge],\n              qNode = frame.qNode.edges[qEdge],\n              final = node.final && qNode.final,\n              next = undefined\n\n          if (nEdge in frame.output.edges) {\n            // an edge already exists for this character\n            // no need to create a new node, just set the finality\n            // bit unless this node is already final\n            next = frame.output.edges[nEdge]\n            next.final = next.final || final\n\n          } else {\n            // no edge exists yet, must create one\n            // set the finality bit and insert it\n            // into the output\n            next = new lunr.TokenSet\n            next.final = final\n            frame.output.edges[nEdge] = next\n          }\n\n          stack.push({\n            qNode: qNode,\n            output: next,\n            node: node\n          })\n        }\n      }\n    }\n  }\n\n  return output\n}\nlunr.TokenSet.Builder = function () {\n  this.previousWord = \"\"\n  this.root = new lunr.TokenSet\n  this.uncheckedNodes = []\n  this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n  var node,\n      commonPrefix = 0\n\n  if (word < this.previousWord) {\n    throw new Error (\"Out of order word insertion\")\n  }\n\n  for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n    if (word[i] != this.previousWord[i]) break\n    commonPrefix++\n  }\n\n  this.minimize(commonPrefix)\n\n  if (this.uncheckedNodes.length == 0) {\n    node = this.root\n  } else {\n    node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n  }\n\n  for (var i = commonPrefix; i < word.length; i++) {\n    var nextNode = new lunr.TokenSet,\n        char = word[i]\n\n    node.edges[char] = nextNode\n\n    this.uncheckedNodes.push({\n      parent: node,\n      char: char,\n      child: nextNode\n    })\n\n    node = nextNode\n  }\n\n  node.final = true\n  this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n  this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n  for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n    var node = this.uncheckedNodes[i],\n        childKey = node.child.toString()\n\n    if (childKey in this.minimizedNodes) {\n      node.parent.edges[node.char] = this.minimizedNodes[childKey]\n    } else {\n      // Cache the key for this node since\n      // we know it can't change anymore\n      node.child._str = childKey\n\n      this.minimizedNodes[childKey] = node.child\n    }\n\n    this.uncheckedNodes.pop()\n  }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n  this.invertedIndex = attrs.invertedIndex\n  this.fieldVectors = attrs.fieldVectors\n  this.tokenSet = attrs.tokenSet\n  this.fields = attrs.fields\n  this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example <caption>Simple single term query</caption>\n * hello\n * @example <caption>Multiple term query</caption>\n * hello world\n * @example <caption>term scoped to a field</caption>\n * title:hello\n * @example <caption>term with a boost of 10</caption>\n * hello^10\n * @example <caption>term with an edit distance of 2</caption>\n * hello~2\n * @example <caption>terms with presence modifiers</caption>\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first.  For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n  return this.query(function (query) {\n    var parser = new lunr.QueryParser(queryString, query)\n    parser.parse()\n  })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n  // for each query clause\n  // * process terms\n  // * expand terms from token set\n  // * find matching documents and metadata\n  // * get document vectors\n  // * score documents\n\n  var query = new lunr.Query(this.fields),\n      matchingFields = Object.create(null),\n      queryVectors = Object.create(null),\n      termFieldCache = Object.create(null),\n      requiredMatches = Object.create(null),\n      prohibitedMatches = Object.create(null)\n\n  /*\n   * To support field level boosts a query vector is created per\n   * field. An empty vector is eagerly created to support negated\n   * queries.\n   */\n  for (var i = 0; i < this.fields.length; i++) {\n    queryVectors[this.fields[i]] = new lunr.Vector\n  }\n\n  fn.call(query, query)\n\n  for (var i = 0; i < query.clauses.length; i++) {\n    /*\n     * Unless the pipeline has been disabled for this term, which is\n     * the case for terms with wildcards, we need to pass the clause\n     * term through the search pipeline. A pipeline returns an array\n     * of processed terms. Pipeline functions may expand the passed\n     * term, which means we may end up performing multiple index lookups\n     * for a single query term.\n     */\n    var clause = query.clauses[i],\n        terms = null,\n        clauseMatches = lunr.Set.empty\n\n    if (clause.usePipeline) {\n      terms = this.pipeline.runString(clause.term, {\n        fields: clause.fields\n      })\n    } else {\n      terms = [clause.term]\n    }\n\n    for (var m = 0; m < terms.length; m++) {\n      var term = terms[m]\n\n      /*\n       * Each term returned from the pipeline needs to use the same query\n       * clause object, e.g. the same boost and or edit distance. The\n       * simplest way to do this is to re-use the clause object but mutate\n       * its term property.\n       */\n      clause.term = term\n\n      /*\n       * From the term in the clause we create a token set which will then\n       * be used to intersect the indexes token set to get a list of terms\n       * to lookup in the inverted index\n       */\n      var termTokenSet = lunr.TokenSet.fromClause(clause),\n          expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n      /*\n       * If a term marked as required does not exist in the tokenSet it is\n       * impossible for the search to return any matches. We set all the field\n       * scoped required matches set to empty and stop examining any further\n       * clauses.\n       */\n      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n        for (var k = 0; k < clause.fields.length; k++) {\n          var field = clause.fields[k]\n          requiredMatches[field] = lunr.Set.empty\n        }\n\n        break\n      }\n\n      for (var j = 0; j < expandedTerms.length; j++) {\n        /*\n         * For each term get the posting and termIndex, this is required for\n         * building the query vector.\n         */\n        var expandedTerm = expandedTerms[j],\n            posting = this.invertedIndex[expandedTerm],\n            termIndex = posting._index\n\n        for (var k = 0; k < clause.fields.length; k++) {\n          /*\n           * For each field that this query term is scoped by (by default\n           * all fields are in scope) we need to get all the document refs\n           * that have this term in that field.\n           *\n           * The posting is the entry in the invertedIndex for the matching\n           * term from above.\n           */\n          var field = clause.fields[k],\n              fieldPosting = posting[field],\n              matchingDocumentRefs = Object.keys(fieldPosting),\n              termField = expandedTerm + \"/\" + field,\n              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n          /*\n           * if the presence of this term is required ensure that the matching\n           * documents are added to the set of required matches for this clause.\n           *\n           */\n          if (clause.presence == lunr.Query.presence.REQUIRED) {\n            clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n            if (requiredMatches[field] === undefined) {\n              requiredMatches[field] = lunr.Set.complete\n            }\n          }\n\n          /*\n           * if the presence of this term is prohibited ensure that the matching\n           * documents are added to the set of prohibited matches for this field,\n           * creating that set if it does not yet exist.\n           */\n          if (clause.presence == lunr.Query.presence.PROHIBITED) {\n            if (prohibitedMatches[field] === undefined) {\n              prohibitedMatches[field] = lunr.Set.empty\n            }\n\n            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n            /*\n             * Prohibited matches should not be part of the query vector used for\n             * similarity scoring and no metadata should be extracted so we continue\n             * to the next field\n             */\n            continue\n          }\n\n          /*\n           * The query field vector is populated using the termIndex found for\n           * the term and a unit value with the appropriate boost applied.\n           * Using upsert because there could already be an entry in the vector\n           * for the term we are working with. In that case we just add the scores\n           * together.\n           */\n          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n          /**\n           * If we've already seen this term, field combo then we've already collected\n           * the matching documents and metadata, no need to go through all that again\n           */\n          if (termFieldCache[termField]) {\n            continue\n          }\n\n          for (var l = 0; l < matchingDocumentRefs.length; l++) {\n            /*\n             * All metadata for this term/field/document triple\n             * are then extracted and collected into an instance\n             * of lunr.MatchData ready to be returned in the query\n             * results\n             */\n            var matchingDocumentRef = matchingDocumentRefs[l],\n                matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n                metadata = fieldPosting[matchingDocumentRef],\n                fieldMatch\n\n            if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n              matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n            } else {\n              fieldMatch.add(expandedTerm, field, metadata)\n            }\n\n          }\n\n          termFieldCache[termField] = true\n        }\n      }\n    }\n\n    /**\n     * If the presence was required we need to update the requiredMatches field sets.\n     * We do this after all fields for the term have collected their matches because\n     * the clause terms presence is required in _any_ of the fields not _all_ of the\n     * fields.\n     */\n    if (clause.presence === lunr.Query.presence.REQUIRED) {\n      for (var k = 0; k < clause.fields.length; k++) {\n        var field = clause.fields[k]\n        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n      }\n    }\n  }\n\n  /**\n   * Need to combine the field scoped required and prohibited\n   * matching documents into a global set of required and prohibited\n   * matches\n   */\n  var allRequiredMatches = lunr.Set.complete,\n      allProhibitedMatches = lunr.Set.empty\n\n  for (var i = 0; i < this.fields.length; i++) {\n    var field = this.fields[i]\n\n    if (requiredMatches[field]) {\n      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n    }\n\n    if (prohibitedMatches[field]) {\n      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n    }\n  }\n\n  var matchingFieldRefs = Object.keys(matchingFields),\n      results = [],\n      matches = Object.create(null)\n\n  /*\n   * If the query is negated (contains only prohibited terms)\n   * we need to get _all_ fieldRefs currently existing in the\n   * index. This is only done when we know that the query is\n   * entirely prohibited terms to avoid any cost of getting all\n   * fieldRefs unnecessarily.\n   *\n   * Additionally, blank MatchData must be created to correctly\n   * populate the results.\n   */\n  if (query.isNegated()) {\n    matchingFieldRefs = Object.keys(this.fieldVectors)\n\n    for (var i = 0; i < matchingFieldRefs.length; i++) {\n      var matchingFieldRef = matchingFieldRefs[i]\n      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n      matchingFields[matchingFieldRef] = new lunr.MatchData\n    }\n  }\n\n  for (var i = 0; i < matchingFieldRefs.length; i++) {\n    /*\n     * Currently we have document fields that match the query, but we\n     * need to return documents. The matchData and scores are combined\n     * from multiple fields belonging to the same document.\n     *\n     * Scores are calculated by field, using the query vectors created\n     * above, and combined into a final document score using addition.\n     */\n    var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n        docRef = fieldRef.docRef\n\n    if (!allRequiredMatches.contains(docRef)) {\n      continue\n    }\n\n    if (allProhibitedMatches.contains(docRef)) {\n      continue\n    }\n\n    var fieldVector = this.fieldVectors[fieldRef],\n        score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n        docMatch\n\n    if ((docMatch = matches[docRef]) !== undefined) {\n      docMatch.score += score\n      docMatch.matchData.combine(matchingFields[fieldRef])\n    } else {\n      var match = {\n        ref: docRef,\n        score: score,\n        matchData: matchingFields[fieldRef]\n      }\n      matches[docRef] = match\n      results.push(match)\n    }\n  }\n\n  /*\n   * Sort the results objects by score, highest first.\n   */\n  return results.sort(function (a, b) {\n    return b.score - a.score\n  })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n  var invertedIndex = Object.keys(this.invertedIndex)\n    .sort()\n    .map(function (term) {\n      return [term, this.invertedIndex[term]]\n    }, this)\n\n  var fieldVectors = Object.keys(this.fieldVectors)\n    .map(function (ref) {\n      return [ref, this.fieldVectors[ref].toJSON()]\n    }, this)\n\n  return {\n    version: lunr.version,\n    fields: this.fields,\n    fieldVectors: fieldVectors,\n    invertedIndex: invertedIndex,\n    pipeline: this.pipeline.toJSON()\n  }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n  var attrs = {},\n      fieldVectors = {},\n      serializedVectors = serializedIndex.fieldVectors,\n      invertedIndex = Object.create(null),\n      serializedInvertedIndex = serializedIndex.invertedIndex,\n      tokenSetBuilder = new lunr.TokenSet.Builder,\n      pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n  if (serializedIndex.version != lunr.version) {\n    lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n  }\n\n  for (var i = 0; i < serializedVectors.length; i++) {\n    var tuple = serializedVectors[i],\n        ref = tuple[0],\n        elements = tuple[1]\n\n    fieldVectors[ref] = new lunr.Vector(elements)\n  }\n\n  for (var i = 0; i < serializedInvertedIndex.length; i++) {\n    var tuple = serializedInvertedIndex[i],\n        term = tuple[0],\n        posting = tuple[1]\n\n    tokenSetBuilder.insert(term)\n    invertedIndex[term] = posting\n  }\n\n  tokenSetBuilder.finish()\n\n  attrs.fields = serializedIndex.fields\n\n  attrs.fieldVectors = fieldVectors\n  attrs.invertedIndex = invertedIndex\n  attrs.tokenSet = tokenSetBuilder.root\n  attrs.pipeline = pipeline\n\n  return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n  this._ref = \"id\"\n  this._fields = Object.create(null)\n  this._documents = Object.create(null)\n  this.invertedIndex = Object.create(null)\n  this.fieldTermFrequencies = {}\n  this.fieldLengths = {}\n  this.tokenizer = lunr.tokenizer\n  this.pipeline = new lunr.Pipeline\n  this.searchPipeline = new lunr.Pipeline\n  this.documentCount = 0\n  this._b = 0.75\n  this._k1 = 1.2\n  this.termIndex = 0\n  this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n  this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example <caption>Extracting a nested field</caption>\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n  if (/\\//.test(fieldName)) {\n    throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n  }\n\n  this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n  if (number < 0) {\n    this._b = 0\n  } else if (number > 1) {\n    this._b = 1\n  } else {\n    this._b = number\n  }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n  this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n  var docRef = doc[this._ref],\n      fields = Object.keys(this._fields)\n\n  this._documents[docRef] = attributes || {}\n  this.documentCount += 1\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i],\n        extractor = this._fields[fieldName].extractor,\n        field = extractor ? extractor(doc) : doc[fieldName],\n        tokens = this.tokenizer(field, {\n          fields: [fieldName]\n        }),\n        terms = this.pipeline.run(tokens),\n        fieldRef = new lunr.FieldRef (docRef, fieldName),\n        fieldTerms = Object.create(null)\n\n    this.fieldTermFrequencies[fieldRef] = fieldTerms\n    this.fieldLengths[fieldRef] = 0\n\n    // store the length of this field for this document\n    this.fieldLengths[fieldRef] += terms.length\n\n    // calculate term frequencies for this field\n    for (var j = 0; j < terms.length; j++) {\n      var term = terms[j]\n\n      if (fieldTerms[term] == undefined) {\n        fieldTerms[term] = 0\n      }\n\n      fieldTerms[term] += 1\n\n      // add to inverted index\n      // create an initial posting if one doesn't exist\n      if (this.invertedIndex[term] == undefined) {\n        var posting = Object.create(null)\n        posting[\"_index\"] = this.termIndex\n        this.termIndex += 1\n\n        for (var k = 0; k < fields.length; k++) {\n          posting[fields[k]] = Object.create(null)\n        }\n\n        this.invertedIndex[term] = posting\n      }\n\n      // add an entry for this term/fieldName/docRef to the invertedIndex\n      if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n        this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n      }\n\n      // store all whitelisted metadata about this token in the\n      // inverted index\n      for (var l = 0; l < this.metadataWhitelist.length; l++) {\n        var metadataKey = this.metadataWhitelist[l],\n            metadata = term.metadata[metadataKey]\n\n        if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n          this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n        }\n\n        this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n      }\n    }\n\n  }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n  var fieldRefs = Object.keys(this.fieldLengths),\n      numberOfFields = fieldRefs.length,\n      accumulator = {},\n      documentsWithField = {}\n\n  for (var i = 0; i < numberOfFields; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        field = fieldRef.fieldName\n\n    documentsWithField[field] || (documentsWithField[field] = 0)\n    documentsWithField[field] += 1\n\n    accumulator[field] || (accumulator[field] = 0)\n    accumulator[field] += this.fieldLengths[fieldRef]\n  }\n\n  var fields = Object.keys(this._fields)\n\n  for (var i = 0; i < fields.length; i++) {\n    var fieldName = fields[i]\n    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n  }\n\n  this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n  var fieldVectors = {},\n      fieldRefs = Object.keys(this.fieldTermFrequencies),\n      fieldRefsLength = fieldRefs.length,\n      termIdfCache = Object.create(null)\n\n  for (var i = 0; i < fieldRefsLength; i++) {\n    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n        fieldName = fieldRef.fieldName,\n        fieldLength = this.fieldLengths[fieldRef],\n        fieldVector = new lunr.Vector,\n        termFrequencies = this.fieldTermFrequencies[fieldRef],\n        terms = Object.keys(termFrequencies),\n        termsLength = terms.length\n\n\n    var fieldBoost = this._fields[fieldName].boost || 1,\n        docBoost = this._documents[fieldRef.docRef].boost || 1\n\n    for (var j = 0; j < termsLength; j++) {\n      var term = terms[j],\n          tf = termFrequencies[term],\n          termIndex = this.invertedIndex[term]._index,\n          idf, score, scoreWithPrecision\n\n      if (termIdfCache[term] === undefined) {\n        idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n        termIdfCache[term] = idf\n      } else {\n        idf = termIdfCache[term]\n      }\n\n      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n      score *= fieldBoost\n      score *= docBoost\n      scoreWithPrecision = Math.round(score * 1000) / 1000\n      // Converts 1.23456789 to 1.234.\n      // Reducing the precision so that the vectors take up less\n      // space when serialised. Doing it now so that they behave\n      // the same before and after serialisation. Also, this is\n      // the fastest approach to reducing a number's precision in\n      // JavaScript.\n\n      fieldVector.insert(termIndex, scoreWithPrecision)\n    }\n\n    fieldVectors[fieldRef] = fieldVector\n  }\n\n  this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n  this.tokenSet = lunr.TokenSet.fromArray(\n    Object.keys(this.invertedIndex).sort()\n  )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n  this.calculateAverageFieldLengths()\n  this.createFieldVectors()\n  this.createTokenSet()\n\n  return new lunr.Index({\n    invertedIndex: this.invertedIndex,\n    fieldVectors: this.fieldVectors,\n    tokenSet: this.tokenSet,\n    fields: Object.keys(this._fields),\n    pipeline: this.searchPipeline\n  })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n  var args = Array.prototype.slice.call(arguments, 1)\n  args.unshift(this)\n  fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n  var clonedMetadata = Object.create(null),\n      metadataKeys = Object.keys(metadata || {})\n\n  // Cloning the metadata to prevent the original\n  // being mutated during match data combination.\n  // Metadata is kept in an array within the inverted\n  // index so cloning the data can be done with\n  // Array#slice\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n    clonedMetadata[key] = metadata[key].slice()\n  }\n\n  this.metadata = Object.create(null)\n\n  if (term !== undefined) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = clonedMetadata\n  }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n  var terms = Object.keys(otherMatchData.metadata)\n\n  for (var i = 0; i < terms.length; i++) {\n    var term = terms[i],\n        fields = Object.keys(otherMatchData.metadata[term])\n\n    if (this.metadata[term] == undefined) {\n      this.metadata[term] = Object.create(null)\n    }\n\n    for (var j = 0; j < fields.length; j++) {\n      var field = fields[j],\n          keys = Object.keys(otherMatchData.metadata[term][field])\n\n      if (this.metadata[term][field] == undefined) {\n        this.metadata[term][field] = Object.create(null)\n      }\n\n      for (var k = 0; k < keys.length; k++) {\n        var key = keys[k]\n\n        if (this.metadata[term][field][key] == undefined) {\n          this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n        } else {\n          this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n        }\n\n      }\n    }\n  }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n  if (!(term in this.metadata)) {\n    this.metadata[term] = Object.create(null)\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  if (!(field in this.metadata[term])) {\n    this.metadata[term][field] = metadata\n    return\n  }\n\n  var metadataKeys = Object.keys(metadata)\n\n  for (var i = 0; i < metadataKeys.length; i++) {\n    var key = metadataKeys[i]\n\n    if (key in this.metadata[term][field]) {\n      this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n    } else {\n      this.metadata[term][field][key] = metadata[key]\n    }\n  }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n  this.clauses = []\n  this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with trailing wildcard</caption>\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example <caption>query term with leading and trailing wildcard</caption>\n * query.term('foo', {\n *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example <caption>query term with required presence</caption>\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n  /**\n   * Term's presence in a document is optional, this is the default value.\n   */\n  OPTIONAL: 1,\n\n  /**\n   * Term's presence in a document is required, documents that do not contain\n   * this term will not be returned.\n   */\n  REQUIRED: 2,\n\n  /**\n   * Term's presence in a document is prohibited, documents that do contain\n   * this term will not be returned.\n   */\n  PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n  if (!('fields' in clause)) {\n    clause.fields = this.allFields\n  }\n\n  if (!('boost' in clause)) {\n    clause.boost = 1\n  }\n\n  if (!('usePipeline' in clause)) {\n    clause.usePipeline = true\n  }\n\n  if (!('wildcard' in clause)) {\n    clause.wildcard = lunr.Query.wildcard.NONE\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n    clause.term = \"*\" + clause.term\n  }\n\n  if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n    clause.term = \"\" + clause.term + \"*\"\n  }\n\n  if (!('presence' in clause)) {\n    clause.presence = lunr.Query.presence.OPTIONAL\n  }\n\n  this.clauses.push(clause)\n\n  return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n  for (var i = 0; i < this.clauses.length; i++) {\n    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n      return false\n    }\n  }\n\n  return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example <caption>adding a single term to a query</caption>\n * query.term(\"foo\")\n * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>\n * query.term(\"foo\", {\n *   fields: [\"title\"],\n *   boost: 10,\n *   wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n  if (Array.isArray(term)) {\n    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n    return this\n  }\n\n  var clause = options || {}\n  clause.term = term.toString()\n\n  this.clause(clause)\n\n  return this\n}\nlunr.QueryParseError = function (message, start, end) {\n  this.name = \"QueryParseError\"\n  this.message = message\n  this.start = start\n  this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n  this.lexemes = []\n  this.str = str\n  this.length = str.length\n  this.pos = 0\n  this.start = 0\n  this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n  var state = lunr.QueryLexer.lexText\n\n  while (state) {\n    state = state(this)\n  }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n  var subSlices = [],\n      sliceStart = this.start,\n      sliceEnd = this.pos\n\n  for (var i = 0; i < this.escapeCharPositions.length; i++) {\n    sliceEnd = this.escapeCharPositions[i]\n    subSlices.push(this.str.slice(sliceStart, sliceEnd))\n    sliceStart = sliceEnd + 1\n  }\n\n  subSlices.push(this.str.slice(sliceStart, this.pos))\n  this.escapeCharPositions.length = 0\n\n  return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n  this.lexemes.push({\n    type: type,\n    str: this.sliceString(),\n    start: this.start,\n    end: this.pos\n  })\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n  this.escapeCharPositions.push(this.pos - 1)\n  this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n  if (this.pos >= this.length) {\n    return lunr.QueryLexer.EOS\n  }\n\n  var char = this.str.charAt(this.pos)\n  this.pos += 1\n  return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n  return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n  if (this.start == this.pos) {\n    this.pos += 1\n  }\n\n  this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n  this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n  var char, charCode\n\n  do {\n    char = this.next()\n    charCode = char.charCodeAt(0)\n  } while (charCode > 47 && charCode < 58)\n\n  if (char != lunr.QueryLexer.EOS) {\n    this.backup()\n  }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n  return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n  lexer.backup()\n  lexer.emit(lunr.QueryLexer.FIELD)\n  lexer.ignore()\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n  if (lexer.width() > 1) {\n    lexer.backup()\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n\n  lexer.ignore()\n\n  if (lexer.more()) {\n    return lunr.QueryLexer.lexText\n  }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n  lexer.ignore()\n  lexer.acceptDigitRun()\n  lexer.emit(lunr.QueryLexer.BOOST)\n  return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n  if (lexer.width() > 0) {\n    lexer.emit(lunr.QueryLexer.TERM)\n  }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n  while (true) {\n    var char = lexer.next()\n\n    if (char == lunr.QueryLexer.EOS) {\n      return lunr.QueryLexer.lexEOS\n    }\n\n    // Escape character is '\\'\n    if (char.charCodeAt(0) == 92) {\n      lexer.escapeCharacter()\n      continue\n    }\n\n    if (char == \":\") {\n      return lunr.QueryLexer.lexField\n    }\n\n    if (char == \"~\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexEditDistance\n    }\n\n    if (char == \"^\") {\n      lexer.backup()\n      if (lexer.width() > 0) {\n        lexer.emit(lunr.QueryLexer.TERM)\n      }\n      return lunr.QueryLexer.lexBoost\n    }\n\n    // \"+\" indicates term presence is required\n    // checking for length to ensure that only\n    // leading \"+\" are considered\n    if (char == \"+\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    // \"-\" indicates term presence is prohibited\n    // checking for length to ensure that only\n    // leading \"-\" are considered\n    if (char == \"-\" && lexer.width() === 1) {\n      lexer.emit(lunr.QueryLexer.PRESENCE)\n      return lunr.QueryLexer.lexText\n    }\n\n    if (char.match(lunr.QueryLexer.termSeparator)) {\n      return lunr.QueryLexer.lexTerm\n    }\n  }\n}\n\nlunr.QueryParser = function (str, query) {\n  this.lexer = new lunr.QueryLexer (str)\n  this.query = query\n  this.currentClause = {}\n  this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n  this.lexer.run()\n  this.lexemes = this.lexer.lexemes\n\n  var state = lunr.QueryParser.parseClause\n\n  while (state) {\n    state = state(this)\n  }\n\n  return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n  return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n  var lexeme = this.peekLexeme()\n  this.lexemeIdx += 1\n  return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n  var completedClause = this.currentClause\n  this.query.clause(completedClause)\n  this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n  var lexeme = parser.peekLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.type) {\n    case lunr.QueryLexer.PRESENCE:\n      return lunr.QueryParser.parsePresence\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n      if (lexeme.str.length >= 1) {\n        errorMessage += \" with value '\" + lexeme.str + \"'\"\n      }\n\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  switch (lexeme.str) {\n    case \"-\":\n      parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n      break\n    case \"+\":\n      parser.currentClause.presence = lunr.Query.presence.REQUIRED\n      break\n    default:\n      var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term or field, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.FIELD:\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n    var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n        errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.fields = [lexeme.str]\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    var errorMessage = \"expecting term, found nothing\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      return lunr.QueryParser.parseTerm\n    default:\n      var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  parser.currentClause.term = lexeme.str.toLowerCase()\n\n  if (lexeme.str.indexOf(\"*\") != -1) {\n    parser.currentClause.usePipeline = false\n  }\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var editDistance = parseInt(lexeme.str, 10)\n\n  if (isNaN(editDistance)) {\n    var errorMessage = \"edit distance must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.editDistance = editDistance\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n  var lexeme = parser.consumeLexeme()\n\n  if (lexeme == undefined) {\n    return\n  }\n\n  var boost = parseInt(lexeme.str, 10)\n\n  if (isNaN(boost)) {\n    var errorMessage = \"boost must be numeric\"\n    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n  }\n\n  parser.currentClause.boost = boost\n\n  var nextLexeme = parser.peekLexeme()\n\n  if (nextLexeme == undefined) {\n    parser.nextClause()\n    return\n  }\n\n  switch (nextLexeme.type) {\n    case lunr.QueryLexer.TERM:\n      parser.nextClause()\n      return lunr.QueryParser.parseTerm\n    case lunr.QueryLexer.FIELD:\n      parser.nextClause()\n      return lunr.QueryParser.parseField\n    case lunr.QueryLexer.EDIT_DISTANCE:\n      return lunr.QueryParser.parseEditDistance\n    case lunr.QueryLexer.BOOST:\n      return lunr.QueryParser.parseBoost\n    case lunr.QueryLexer.PRESENCE:\n      parser.nextClause()\n      return lunr.QueryParser.parsePresence\n    default:\n      var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n  }\n}\n\n  /**\n   * export the module via AMD, CommonJS or as a browser global\n   * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n   */\n  ;(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n      // AMD. Register as an anonymous module.\n      define(factory)\n    } else if (typeof exports === 'object') {\n      /**\n       * Node. Does not work with strict CommonJS, but\n       * only CommonJS-like enviroments that support module.exports,\n       * like Node.\n       */\n      module.exports = factory()\n    } else {\n      // Browser globals (root is window)\n      root.lunr = factory()\n    }\n  }(this, function () {\n    /**\n     * Just return a value to define the module export.\n     * This example returns an object, but the module\n     * can return a function as the exported value.\n     */\n    return lunr\n  }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n  var str = '' + string;\n  var match = matchHtmlRegExp.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  var escape;\n  var html = '';\n  var index = 0;\n  var lastIndex = 0;\n\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;';\n        break;\n      case 38: // &\n        escape = '&amp;';\n        break;\n      case 39: // '\n        escape = '&#39;';\n        break;\n      case 60: // <\n        escape = '&lt;';\n        break;\n      case 62: // >\n        escape = '&gt;';\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.substring(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escape;\n  }\n\n  return lastIndex !== index\n    ? html + str.substring(lastIndex, index)\n    : html;\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n  SearchMessage,\n  SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n  function importScripts(...urls: string[]): Promise<void> | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n  config: SearchIndexConfig\n): Promise<void> {\n  let base = \"../lunr\"\n\n  /* Detect `iframe-worker` and fix base URL */\n  if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n    const worker = document.querySelector<HTMLScriptElement>(\"script[src]\")!\n    const [path] = worker.src.split(\"/worker\")\n\n    /* Prefix base with path */\n    base = base.replace(\"..\", path)\n  }\n\n  /* Add scripts for languages */\n  const scripts = []\n  for (const lang of config.lang) {\n    if (lang === \"ja\") scripts.push(`${base}/tinyseg.js`)\n    if (lang !== \"en\") scripts.push(`${base}/min/lunr.${lang}.min.js`)\n  }\n\n  /* Add multi-language support */\n  if (config.lang.length > 1)\n    scripts.push(`${base}/min/lunr.multi.min.js`)\n\n  /* Load scripts synchronously */\n  if (scripts.length)\n    await importScripts(\n      `${base}/min/lunr.stemmer.support.min.js`,\n      ...scripts\n    )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n  message: SearchMessage\n): Promise<SearchMessage> {\n  switch (message.type) {\n\n    /* Search setup message */\n    case SearchMessageType.SETUP:\n      await setupSearchLanguages(message.data.config)\n      index = new Search(message.data)\n      return {\n        type: SearchMessageType.READY\n      }\n\n    /* Search query message */\n    case SearchMessageType.QUERY:\n      return {\n        type: SearchMessageType.RESULT,\n        data: index ? index.search(message.data) : []\n      }\n\n    /* All other messages */\n    default:\n      throw new TypeError(\"Invalid message type\")\n  }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-ignore - expose Lunr.js in global scope, or stemmers will not work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n  postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n  parent?: SearchIndexDocument         /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map<string, SearchDocument>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n  docs: SearchIndexDocument[]\n): SearchDocumentMap {\n  const documents = new Map<string, SearchDocument>()\n  const parents   = new Set<SearchDocument>()\n  for (const doc of docs) {\n    const [path, hash] = doc.location.split(\"#\")\n\n    /* Extract location and title */\n    const location = doc.location\n    const title    = doc.title\n\n    /* Escape and cleanup text */\n    const text = escapeHTML(doc.text)\n      .replace(/\\s+(?=[,.:;!?])/g, \"\")\n      .replace(/\\s+/g, \" \")\n\n    /* Handle section */\n    if (hash) {\n      const parent = documents.get(path)!\n\n      /* Ignore first section, override article */\n      if (!parents.has(parent)) {\n        parent.title = doc.title\n        parent.text  = text\n\n        /* Remember that we processed the article */\n        parents.add(parent)\n\n      /* Add subsequent section */\n      } else {\n        documents.set(location, {\n          location,\n          title,\n          text,\n          parent\n        })\n      }\n\n    /* Add article */\n    } else {\n      documents.set(location, {\n        location,\n        title,\n        text\n      })\n    }\n  }\n  return documents\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n  config: SearchIndexConfig\n): SearchHighlightFactoryFn {\n  const separator = new RegExp(config.separator, \"img\")\n  const highlight = (_: unknown, data: string, term: string) => {\n    return `${data}<mark data-md-highlight>${term}</mark>`\n  }\n\n  /* Return factory function */\n  return (query: string) => {\n    query = query\n      .replace(/[\\s*+\\-:~^]+/g, \" \")\n      .trim()\n\n    /* Create search term match expression */\n    const match = new RegExp(`(^|${config.separator})(${\n      query\n        .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n        .replace(separator, \"|\")\n    })`, \"img\")\n\n    /* Highlight string value */\n    return value => value\n      .replace(match, highlight)\n      .replace(/<\\/mark>(\\s+)<mark[^>]*>/img, \"$1\")\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n  presence: lunr.Query.presence        /* Clause presence */\n  term: string                         /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record<string, boolean>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n  value: string\n): SearchQueryClause[] {\n  const query  = new (lunr as any).Query([\"title\", \"text\"])\n  const parser = new (lunr as any).QueryParser(value, query)\n\n  /* Parse and return query clauses */\n  parser.parse()\n  return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n  query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n  const clauses = new Set<SearchQueryClause>(query)\n\n  /* Match query clauses against terms */\n  const result: SearchQueryTerms = {}\n  for (let t = 0; t < terms.length; t++)\n    for (const clause of clauses)\n      if (terms[t].startsWith(clause.term)) {\n        result[clause.term] = true\n        clauses.delete(clause)\n      }\n\n  /* Annotate unmatched query clauses */\n  for (const clause of clauses)\n    result[clause.term] = false\n\n  /* Return query terms */\n  return result\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n  SearchDocument,\n  SearchDocumentMap,\n  setupSearchDocumentMap\n} from \"../document\"\nimport {\n  SearchHighlightFactoryFn,\n  setupSearchHighlighter\n} from \"../highlighter\"\nimport {\n  SearchQueryTerms,\n  getSearchQueryTerms,\n  parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n  lang: string[]                       /* Search languages */\n  separator: string                    /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n  location: string                     /* Document location */\n  title: string                        /* Document title */\n  text: string                         /* Document text */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index pipeline function\n */\nexport type SearchIndexPipelineFn =\n  | \"trimmer\"                          /* Trimmer */\n  | \"stopWordFilter\"                   /* Stop word filter */\n  | \"stemmer\"                          /* Stemmer */\n\n/**\n * Search index pipeline\n */\nexport type SearchIndexPipeline = SearchIndexPipelineFn[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n  config: SearchIndexConfig            /* Search index configuration */\n  docs: SearchIndexDocument[]          /* Search index documents */\n  index?: object                       /* Prebuilt index */\n  pipeline?: SearchIndexPipeline       /* Search index pipeline */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n  score: number                        /* Score (relevance) */\n  terms: SearchQueryTerms              /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport type SearchResult = Array<SearchDocument & SearchMetadata>\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n  const [x, y] = [new Set(a), new Set(b)]\n  return [\n    ...new Set([...x].filter(value => !y.has(value)))\n  ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n  /**\n   * Search document mapping\n   *\n   * A mapping of URLs (including hash fragments) to the actual articles and\n   * sections of the documentation. The search document mapping must be created\n   * regardless of whether the index was prebuilt or not, as Lunr.js itself\n   * only stores the actual index.\n   */\n  protected documents: SearchDocumentMap\n\n  /**\n   * Search highlight factory function\n   */\n  protected highlight: SearchHighlightFactoryFn\n\n  /**\n   * The underlying Lunr.js search index\n   */\n  protected index: lunr.Index\n\n  /**\n   * Create the search integration\n   *\n   * @param data - Search index\n   */\n  public constructor({ config, docs, pipeline, index }: SearchIndex) {\n    this.documents = setupSearchDocumentMap(docs)\n    this.highlight = setupSearchHighlighter(config)\n\n    /* Set separator for tokenizer */\n    lunr.tokenizer.separator = new RegExp(config.separator)\n\n    /* If no index was given, create it */\n    if (typeof index === \"undefined\") {\n      this.index = lunr(function () {\n\n        /* Set up multi-language support */\n        if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n          this.use((lunr as any)[config.lang[0]])\n        } else if (config.lang.length > 1) {\n          this.use((lunr as any).multiLanguage(...config.lang))\n        }\n\n        /* Compute functions to be removed from the pipeline */\n        const fns = difference([\n          \"trimmer\", \"stopWordFilter\", \"stemmer\"\n        ], pipeline!)\n\n        /* Remove functions from the pipeline for registered languages */\n        for (const lang of config.lang.map(language => (\n          language === \"en\" ? lunr : (lunr as any)[language]\n        ))) {\n          for (const fn of fns) {\n            this.pipeline.remove(lang[fn])\n            this.searchPipeline.remove(lang[fn])\n          }\n        }\n\n        /* Set up fields and reference */\n        this.field(\"title\", { boost: 1000 })\n        this.field(\"text\")\n        this.ref(\"location\")\n\n        /* Index documents */\n        for (const doc of docs)\n          this.add(doc)\n      })\n\n    /* Handle prebuilt index */\n    } else {\n      this.index = lunr.Index.load(index)\n    }\n  }\n\n  /**\n   * Search for matching documents\n   *\n   * The search index which MkDocs provides is divided up into articles, which\n   * contain the whole content of the individual pages, and sections, which only\n   * contain the contents of the subsections obtained by breaking the individual\n   * pages up at `h1` ... `h6`. As there may be many sections on different pages\n   * with identical titles (for example within this very project, e.g. \"Usage\"\n   * or \"Installation\"), they need to be put into the context of the containing\n   * page. For this reason, section results are grouped within their respective\n   * articles which are the top-level results that are returned.\n   *\n   * @param query - Query value\n   *\n   * @returns Search results\n   */\n  public search(query: string): SearchResult[] {\n    if (query) {\n      try {\n        const highlight = this.highlight(query)\n\n        /* Parse query to extract clauses for analysis */\n        const clauses = parseSearchQuery(query)\n          .filter(clause => (\n            clause.presence !== lunr.Query.presence.PROHIBITED\n          ))\n\n        /* Perform search and post-process results */\n        const groups = this.index.search(`${query}*`)\n\n          /* Apply post-query boosts based on title and search query terms */\n          .reduce<SearchResult>((results, { ref, score, matchData }) => {\n            const document = this.documents.get(ref)\n            if (typeof document !== \"undefined\") {\n              const { location, title, text, parent } = document\n\n              /* Compute and analyze search query terms */\n              const terms = getSearchQueryTerms(\n                clauses,\n                Object.keys(matchData.metadata)\n              )\n\n              /* Highlight title and text and apply post-query boosts */\n              const boost = +!parent + +Object.values(terms).every(t => t)\n              results.push({\n                location,\n                title: highlight(title),\n                text: highlight(text),\n                score: score * (1 + boost),\n                terms\n              })\n            }\n            return results\n          }, [])\n\n          /* Sort search results again after applying boosts */\n          .sort((a, b) => b.score - a.score)\n\n          /* Group search results by page */\n          .reduce((results, result) => {\n            const document = this.documents.get(result.location)\n            if (typeof document !== \"undefined\") {\n              const ref = \"parent\" in document\n                ? document.parent!.location\n                : document.location\n              results.set(ref, [...results.get(ref) || [], result])\n            }\n            return results\n          }, new Map<string, SearchResult>())\n\n        /* Expand grouped search results */\n        return [...groups.values()]\n\n      /* Log errors to console (for now) */\n      } catch {\n        console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n      }\n    }\n\n    /* Return nothing in case of error or empty query */\n    return []\n  }\n}\n", "/*\n * Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n  SETUP,                               /* Search index setup */\n  READY,                               /* Search index ready */\n  QUERY,                               /* Search query */\n  RESULT                               /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n  type: SearchMessageType.SETUP        /* Message type */\n  data: SearchIndex                    /* Message data */\n}\n\n/**\n * A message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n  type: SearchMessageType.READY        /* Message type */\n}\n\n/**\n * A message containing a search query\n */\nexport interface SearchQueryMessage {\n  type: SearchMessageType.QUERY        /* Message type */\n  data: string                         /* Message data */\n}\n\n/**\n * A message containing results for a search query\n */\nexport interface SearchResultMessage {\n  type: SearchMessageType.RESULT       /* Message type */\n  data: SearchResult[]                 /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * A message exchanged with the search worker\n */\nexport type SearchMessage =\n  | SearchSetupMessage\n  | SearchReadyMessage\n  | SearchQueryMessage\n  | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n  message: SearchMessage\n): message is SearchSetupMessage {\n  return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n  message: SearchMessage\n): message is SearchReadyMessage {\n  return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n  message: SearchMessage\n): message is SearchQueryMessage {\n  return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n  message: SearchMessage\n): message is SearchResultMessage {\n  return message.type === SearchMessageType.RESULT\n}\n"],
+  "mappings": "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AAG,GACV,EAAK,EACD,EAAG,KAAK,IACV,GAAI,WAEG,EAAI,KAAK,GAAI,CACtB,GAAI,GAAK,EAAI,KAAK,GAClB,EAAO,EAAG,GAAK,EAAG,GAClB,EAAM,EACF,EAAI,KAAK,IACX,GAAI,GAMR,GADA,EAAK,EACD,EAAG,KAAK,GAAI,CACd,GAAI,GAAK,EAAG,KAAK,GACjB,EAAO,EAAG,GACV,EAAK,EACL,EAAM,EACN,EAAM,EACF,GAAG,KAAK,IAAU,EAAI,KAAK,IAAS,CAAE,EAAI,KAAK,KACjD,GAAI,GAIR,SAAK,EACL,EAAM,EACF,EAAG,KAAK,IAAM,EAAI,KAAK,IACzB,GAAK,EACL,EAAI,EAAE,QAAQ,EAAG,KAKf,GAAW,KACb,GAAI,EAAQ,cAAgB,EAAE,OAAO,IAGhC,GAGT,MAAO,UAAU,EAAO,CACtB,MAAO,GAAM,OAAO,OAIxB,EAAK,SAAS,iBAAiB,EAAK,QAAS,WAC7C,AAkBA,EAAK,uBAAyB,SAAU,EAAW,CACjD,GAAI,GAAQ,EAAU,OAAO,SAAU,EAAM,EAAU,CACrD,SAAK,GAAY,EACV,GACN,IAEH,MAAO,UAAU,EAAO,CACtB,GAAI,GAAS,EAAM,EAAM,cAAgB,EAAM,WAAY,MAAO,KAiBtE,EAAK,eAAiB,EAAK,uBAAuB,CAChD,IACA,OACA,QACA,SACA,QACA,MACA,SACA,OACA,KACA,QACA,KACA,MACA,MACA,MACA,KACA,KACA,KACA,UACA,OACA,MACA,KACA,MACA,SACA,QACA,OACA,MACA,KACA,OACA,SACA,OACA,OACA,QACA,MACA,OACA,MACA,MACA,MACA,MACA,OACA,KACA,MACA,OACA,MACA,MACA,MACA,UACA,IACA,KACA,KACA,OACA,KACA,KACA,MACA,OACA,QACA,MACA,OACA,SACA,MACA,KACA,QACA,OACA,OACA,KACA,UACA,KACA,MACA,MACA,KACA,MACA,QACA,KACA,OACA,KACA,QACA,MACA,MACA,SACA,OACA,MACA,OACA,MACA,SACA,QACA,KACA,OACA,OACA,OACA,MACA,QACA,OACA,OACA,QACA,QACA,OACA,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",
+  "names": []
+}
diff --git a/latest/assets/stylesheets/main.33e2939f.min.css b/latest/assets/stylesheets/main.33e2939f.min.css
new file mode 100644 (file)
index 0000000..54d2163
--- /dev/null
@@ -0,0 +1,2 @@
+@charset "UTF-8";html{box-sizing:border-box;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}*,:after,:before{box-sizing:inherit}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{display:block;box-sizing:initial;height:.05rem;padding:0;overflow:visible;border:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;font-size:inherit;background:transparent;border:0}input{border:0;outline:none}:root{--md-default-fg-color:rgba(0,0,0,0.87);--md-default-fg-color--light:rgba(0,0,0,0.54);--md-default-fg-color--lighter:rgba(0,0,0,0.32);--md-default-fg-color--lightest:rgba(0,0,0,0.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,0.7);--md-default-bg-color--lighter:hsla(0,0%,100%,0.3);--md-default-bg-color--lightest:hsla(0,0%,100%,0.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}:root>*{--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,0.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,0.5);--md-typeset-del-color:rgba(245,80,61,0.15);--md-typeset-ins-color:rgba(11,213,112,0.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,0.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,0.3);--md-footer-bg-color:rgba(0,0,0,0.87);--md-footer-bg-color--dark:rgba(0,0,0,0.32)}.md-icon svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family,_),SFMono-Regular,Consolas,Menlo,monospace}:root{--md-typeset-table--ascending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 4h2v12l5.5-5.5 1.42 1.42L12 19.84l-7.92-7.92L5.5 10.5 11 16V4z'/></svg>");--md-typeset-table--descending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z'/></svg>")}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact;color-adjust:exact}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{display:flow-root;margin:1em 0}.md-typeset h1{margin:0 0 1.25em;color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:1.6em 0 .64em;font-size:1.5625em;line-height:1.4}.md-typeset h3{margin:1.6em 0 .8em;font-weight:400;font-size:1.25em;line-height:1.5;letter-spacing:-.01em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{margin:1em 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{margin:1.25em 0;color:var(--md-default-fg-color--light);font-weight:700;font-size:.8em;letter-spacing:-.01em}.md-typeset h5{text-transform:uppercase}.md-typeset hr{display:flow-root;margin:1.5em 0;border-bottom:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{padding:0 .2941176471em;font-size:.85em;word-break:break-word;background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset code:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:initial;padding:initial;background-color:initial;box-shadow:none}.md-typeset a code{color:currentColor}.md-typeset pre{position:relative;line-height:1.4}.md-typeset pre>code{display:block;margin:0;padding:.7720588235em 1.1764705882em;overflow:auto;word-break:normal;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;touch-action:auto;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:44.9375em){.md-typeset>pre{margin:1em -.8rem}.md-typeset>pre code{border-radius:0}}.md-typeset kbd{display:inline-block;padding:0 .6666666667em;color:var(--md-default-fg-color);font-size:.75em;vertical-align:text-top;word-break:break-word;background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset}.md-typeset mark{color:inherit;word-break:break-word;background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{text-decoration:none;border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:focus:after,.md-typeset abbr[title]:hover:after{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:absolute;left:0;display:inline-block;width:auto;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;max-width:80%;margin-top:2em;padding:.2rem .3rem;color:var(--md-default-bg-color);font-size:.7rem;background-color:var(--md-default-fg-color);border-radius:.1rem;content:attr(title)}}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;color:var(--md-default-fg-color--light);border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid var(--md-default-fg-color--lighter);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1.5em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset img,.md-typeset svg{max-width:100%;height:auto}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset figure{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;margin:0 auto;text-align:center}.md-typeset figure img{display:block}.md-typeset figcaption{max-width:24rem;margin:1em auto 2em;font-style:italic}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){display:inline-block;max-width:100%;overflow:auto;font-size:.64rem;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .05rem rgba(0,0,0,.1);touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.9375em 1.25em;color:var(--md-default-bg-color);vertical-align:top;background-color:var(--md-default-fg-color--light)}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{padding:.9375em 1.25em;vertical-align:top;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset table:not([class]) tr{transition:background-color 125ms}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}.md-typeset table th[role=columnheader]:after{display:inline-block;width:1.2em;height:1.2em;margin-left:.5em;vertical-align:sub;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--ascending);mask-image:var(--md-typeset-table--ascending)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--descending);mask-image:var(--md-typeset-table--descending)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{height:100%;overflow-x:hidden;font-size:125%}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;font-size:.5rem;background-color:var(--md-default-bg-color)}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-state=lock]{position:fixed}}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.md-toggle{display:none}.md-option{position:absolute;width:0;height:0;opacity:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-style:auto}.md-skip{position:fixed;z-index:-1;margin:.5rem;padding:.3rem .5rem;color:var(--md-default-bg-color);font-size:.64rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(.4rem);opacity:0}.md-skip:focus{z-index:10;transform:translateY(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms}@page{margin:25mm}.md-announce{overflow:auto;background-color:var(--md-footer-bg-color)}@media print{.md-announce{display:none}}.md-announce__inner{margin:.6rem auto;padding:0 .8rem;color:var(--md-footer-fg-color);font-size:.7rem}:root{--md-clipboard-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1z'/></svg>")}.md-clipboard{position:absolute;top:.5em;right:.5em;z-index:1;width:1.5em;height:1.5em;color:var(--md-default-fg-color--lightest);border-radius:.1rem;cursor:pointer;transition:color .25s}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{display:block;width:1.125em;height:1.125em;margin:0 auto;background-color:currentColor;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-content{flex-grow:1;overflow:hidden;scroll-padding-top:51.2rem}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){.md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem;margin-left:.8rem}.md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:.8rem;margin-left:1.2rem}}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__button{float:right;margin:.4rem 0 .4rem .4rem;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left;margin-right:.4rem;margin-left:0}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}.md-dialog{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:fixed;right:.8rem;bottom:.8rem;left:auto;z-index:2;min-width:11.1rem;padding:.4rem .6rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(100%);opacity:0;transition:transform 0ms .4s,opacity .4s;pointer-events:none}@media print{.md-dialog{display:none}}[dir=rtl] .md-dialog{right:auto;left:.8rem}.md-dialog[data-md-state=open]{transform:translateY(0);opacity:1;transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s;pointer-events:auto}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-typeset .md-button{display:inline-block;padding:.625em 2em;color:var(--md-primary-fg-color);font-weight:700;border:.1rem solid;border-radius:.1rem;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{color:var(--md-accent-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .md-input{height:1.8rem;padding:0 .6rem;font-size:.8rem;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transition:box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{box-shadow:0 .4rem 1rem rgba(0,0,0,.15),0 .025rem .05rem rgba(0,0,0,.15)}.md-typeset .md-input--stretch{width:100%}.md-header{position:-webkit-sticky;position:sticky;top:0;right:0;left:0;z-index:2;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent}@media print{.md-header{display:none}}.md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header[data-md-state=hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header .focus-visible{outline-color:currentColor}.md-header__inner{display:flex;align-items:center;padding:0 .2rem}.md-header__button{position:relative;z-index:1;margin:.2rem;padding:.4rem;color:currentColor;vertical-align:middle;cursor:pointer;transition:opacity .25s}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{position:absolute;display:flex;max-width:100%;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-header__topic+.md-header__topic{z-index:-1;transform:translateX(1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__title{flex-grow:1;height:2.4rem;margin-right:.4rem;margin-left:1rem;font-size:.9rem;line-height:2.4rem}.md-header__title[data-md-state=active] .md-header__topic{z-index:-1;transform:translateX(-1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic{transform:translateX(1.25rem)}.md-header__title[data-md-state=active] .md-header__topic+.md-header__topic{z-index:0;transform:translateX(0);opacity:1;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:auto}.md-header__title>.md-header__ellipsis{position:relative;width:100%;height:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;white-space:nowrap;transition:max-width 0ms .25s,opacity .25s .25s}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){.md-header__source{display:block;width:11.7rem;max-width:11.7rem;margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem;margin-left:0}}@media screen and (min-width:76.25em){.md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-footer{color:var(--md-footer-fg-color);background-color:var(--md-footer-bg-color)}@media print{.md-footer{display:none}}.md-footer__inner{padding:.2rem;overflow:auto}.md-footer__link{display:flex;padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}@media screen and (min-width:45em){.md-footer__link{width:50%}}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}.md-footer__link--prev{float:left}@media screen and (max-width:44.9375em){.md-footer__link--prev{width:25%}.md-footer__link--prev .md-footer__title{display:none}}[dir=rtl] .md-footer__link--prev{float:right}[dir=rtl] .md-footer__link--prev svg{transform:scaleX(-1)}.md-footer__link--next{float:right;text-align:right}@media screen and (max-width:44.9375em){.md-footer__link--next{width:75%}}[dir=rtl] .md-footer__link--next{float:left;text-align:left}[dir=rtl] .md-footer__link--next svg{transform:scaleX(-1)}.md-footer__title{position:relative;flex-grow:1;max-width:calc(100% - 2.4rem);padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-footer-copyright{width:100%;margin:auto .6rem;padding:.4rem 0;color:var(--md-footer-fg-color--lighter);font-size:.64rem}@media screen and (min-width:45em){.md-footer-copyright{width:auto}}.md-footer-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-footer-social{padding:.6rem 0}}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-footer-social__link svg{max-height:.8rem;vertical-align:-25%;fill:currentColor}:root{--md-nav-icon--prev:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z'/></svg>");--md-nav-icon--next:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>");--md-toc-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9h14V7H3v2m0 4h14v-2H3v2m0 4h14v-2H3v2m16 0h2v-2h-2v2m0-10v2h2V7h-2m0 6h2v-2h-2v2z'/></svg>")}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{width:auto;height:100%}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{display:block;width:2.4rem;height:2.4rem;fill:currentColor}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__link{display:block;margin-top:.625em;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:color 125ms;scroll-snap-align:start}.md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{display:block;width:100%;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);background-color:currentColor}.md-nav--primary .md-nav__link[for=__toc]~.md-nav,.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{position:absolute;top:0;right:0;left:0;z-index:1;display:flex;flex-direction:column;height:100%;background-color:var(--md-default-bg-color)}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;color:var(--md-default-fg-color--light);font-weight:400;line-height:2.4rem;white-space:nowrap;background-color:var(--md-default-fg-color--lightest);cursor:pointer}.md-nav--primary .md-nav__title .md-nav__icon{position:absolute;top:.4rem;left:.4rem;display:block;width:1.2rem;height:1.2rem;margin:.2rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem;left:auto}.md-nav--primary .md-nav__title .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav--primary .md-nav__title~.md-nav__list{overflow-y:auto;background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-nav--primary .md-nav__title .md-logo{position:absolute;top:.2rem;left:.2rem;display:block;margin:.2rem;padding:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-logo{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link .md-nav__icon{position:absolute;top:50%;right:.6rem;width:1.2rem;height:1.2rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{right:auto;left:.6rem}.md-nav--primary .md-nav__link .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:initial}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;transform:translateX(100%);opacity:0;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{transform:translateX(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{display:block;padding:0 .2rem;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color--dark)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:block;padding-right:2.4rem;scroll-snap-align:none}[dir=rtl] .md-nav--integrated .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav,.md-nav__toggle:indeterminate~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__title{display:block;padding:0;pointer-events:none;scroll-snap-align:start}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{float:right;width:.9rem;height:.9rem;transition:transform .25s}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:after{display:inline-block;width:100%;height:100%;vertical-align:-.1rem;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav>.md-nav__title{display:block;padding:0 .6rem;pointer-events:none;scroll-snap-align:start}.md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav__item{padding-right:.6rem}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:block;margin-bottom:1.25em;border-left:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav>.md-nav__title{display:none}}:root{--md-search-result-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7c-.41-.25-.8-.56-1.14-.9-.33-.33-.61-.7-.86-1.1H6V4h7v5h5v1.18c.71.16 1.39.43 2 .82V8l-6-6m6.31 16.9c1.33-2.11.69-4.9-1.4-6.22-2.11-1.33-4.91-.68-6.22 1.4-1.34 2.11-.69 4.89 1.4 6.22 1.46.93 3.32.93 4.79.02L22 23.39 23.39 22l-3.08-3.1m-3.81.1a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5z'/></svg>")}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{z-index:1;opacity:0}@media screen and (max-width:59.9375em){.md-search__overlay{position:absolute;top:.2rem;left:-2.2rem;width:2rem;height:2rem;overflow:hidden;background-color:var(--md-default-bg-color);border-radius:1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;pointer-events:none}[dir=rtl] .md-search__overlay{right:-2.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:rgba(0,0,0,.54);cursor:pointer;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){.md-search__inner{position:fixed;top:0;left:100%;z-index:2;width:100%;height:100%;transform:translateX(5%);opacity:0;transition:right 0ms .3s,left 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;transform:translateX(0);opacity:1;transition:right 0ms 0ms,left 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;transform:translateX(-5%)}}@media screen and (min-width:60em){.md-search__inner{position:relative;float:right;width:11.7rem;padding:.1rem 0;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{position:relative}@media screen and (min-width:60em){.md-search__form{border-radius:.1rem}}.md-search__input{position:relative;z-index:2;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;transition:color .25s,background-color .25s,box-shadow .25s}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s;transition:color .25s}.md-search__input::-moz-placeholder{-moz-transition:color .25s;transition:color .25s}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}[data-md-toggle=search]:checked~.md-header .md-search__input{box-shadow:0 0 .6rem rgba(0,0,0,.07)}@media screen and (max-width:59.9375em){.md-search__input{width:100%;height:2.4rem;font-size:.9rem}}@media screen and (min-width:60em){.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;color:inherit;font-size:.8rem;background-color:rgba(0,0,0,.26);border-radius:.1rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}.md-search__input::-webkit-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{color:var(--md-default-fg-color);text-overflow:clip;background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{position:absolute;z-index:2;width:1.2rem;height:1.2rem;cursor:pointer;transition:color .25s,opacity .25s}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){.md-search__icon[for=__search]{top:.6rem;left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem;left:auto}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}.md-search__icon[type=reset]{top:.3rem;right:.5rem;transform:scale(.75);opacity:0;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:none}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}@media screen and (max-width:59.9375em){.md-search__icon[type=reset]{top:.6rem;right:.8rem}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{transform:scale(1);opacity:1;pointer-events:auto}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;z-index:1;width:100%;overflow:hidden;border-radius:0 0 .1rem .1rem}@media screen and (max-width:59.9375em){.md-search__output{top:2.4rem;bottom:0}}@media screen and (min-width:60em){.md-search__output{top:1.9rem;opacity:0;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}}.md-search__scrollwrap{height:100%;overflow-y:auto;background-color:var(--md-default-bg-color);-webkit-backface-visibility:hidden;backface-visibility:hidden;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1), (max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{padding:0 .8rem;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;background-color:var(--md-default-fg-color--lightest);scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}}.md-search-result__list{margin:0;padding:0;list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;transition:background-color .25s;scroll-snap-align:start}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{display:block;padding:.75em .8rem;color:var(--md-typeset-a-color);font-size:.64rem;outline:0;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__more summary:focus,.md-search-result__more summary:hover{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-search-result__more summary::-webkit-details-marker,.md-search-result__more summary::marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{position:relative;padding:0 .8rem;overflow:hidden}@media screen and (min-width:60em){.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-weight:400;font-size:.8rem;line-height:1.4}.md-search-result__icon{position:absolute;left:0;width:1.2rem;height:1.2rem;margin:.5rem;color:var(--md-default-fg-color--light)}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{display:inline-block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-search-result__icon{right:0;left:auto}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{margin:.5em 0;font-weight:700;font-size:.64rem;line-height:1.6}.md-search-result__teaser{display:-webkit-box;max-height:2rem;margin:.5em 0;overflow:hidden;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}@media screen and (max-width:44.9375em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}.md-search-result__teaser mark{text-decoration:underline;background-color:initial}.md-search-result__terms{margin:.5em 0;font-size:.64rem;font-style:italic}.md-search-result mark{color:var(--md-accent-fg-color);background-color:initial}.md-select{position:relative;z-index:1}.md-select__inner{position:absolute;top:calc(100% - .2rem);left:50%;max-height:0;margin-top:.2rem;color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);transform:translate3d(-50%,.3rem,0);opacity:0;transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;transform:translate3d(-50%,0,0);opacity:1;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s}.md-select__inner:after{position:absolute;top:0;left:50%;width:0;height:0;margin-top:-.2rem;margin-left:-.2rem;border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);content:""}.md-select__list{max-height:inherit;margin:0;padding:0;overflow:auto;font-size:.8rem;list-style-type:none;border-radius:.1rem}.md-select__item{line-height:1.8rem}.md-select__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;cursor:pointer;transition:background-color .25s,color .25s;scroll-snap-align:start}[dir=rtl] .md-select__link{padding-right:.6rem;padding-left:1.2rem}.md-select__link:focus,.md-select__link:hover{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{position:-webkit-sticky;position:sticky;top:2.4rem;flex-shrink:0;align-self:flex-start;width:12.1rem;padding:1.2rem 0}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;z-index:3;display:block;width:12.1rem;height:100%;background-color:var(--md-default-bg-color);transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;-ms-scroll-snap-type:none;scroll-snap-type:none;overflow:hidden}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{position:fixed;top:0;z-index:3;width:0;height:0;background-color:rgba(0,0,0,.54);opacity:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}:root{--md-source-forks-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm0 2.122a2.25 2.25 0 1 0-1.5 0v.878A2.25 2.25 0 0 0 5.75 8.5h1.5v2.128a2.251 2.251 0 1 0 1.5 0V8.5h1.5a2.25 2.25 0 0 0 2.25-2.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75h-4.5A.75.75 0 0 1 5 6.25v-.878zm3.75 7.378a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm3-8.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5z'/></svg>");--md-source-repositories-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 1 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 0 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 0 1 1-1h8zM5 12.25v3.25a.25.25 0 0 0 .4.2l1.45-1.087a.25.25 0 0 1 .3 0L8.6 15.7a.25.25 0 0 0 .4-.2v-3.25a.25.25 0 0 0-.25-.25h-3.5a.25.25 0 0 0-.25.25z'/></svg>");--md-source-stars-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694v.001z'/></svg>");--md-source-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill-rule='evenodd' d='M2.5 7.775V2.75a.25.25 0 0 1 .25-.25h5.025a.25.25 0 0 1 .177.073l6.25 6.25a.25.25 0 0 1 0 .354l-5.025 5.025a.25.25 0 0 1-.354 0l-6.25-6.25a.25.25 0 0 1-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.75 1.75 0 0 1 1 7.775zM6 5a1 1 0 1 0 0 2 1 1 0 0 0 0-2z'/></svg>")}.md-source{display:block;font-size:.65rem;line-height:1.2;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity .25s}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;width:2rem;height:2.4rem;vertical-align:middle}.md-source__icon svg{margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);margin-left:.6rem;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{margin:.1rem 0 0;padding:0;overflow:hidden;font-size:.55rem;list-style-type:none;opacity:.75}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{display:inline-block}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{display:inline-block;width:.6rem;height:.6rem;margin-right:.1rem;vertical-align:text-top;background-color:currentColor;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-source__fact:nth-child(1n+2):before{margin-left:.4rem}[dir=rtl] .md-source__fact{margin-right:0;margin-left:.1rem}[dir=rtl] .md-source__fact:nth-child(1n+2):before{margin-right:.4rem;margin-left:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-tabs{width:100%;overflow:auto;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs__list{margin:0 0 0 .2rem;padding:0;white-space:nowrap;list-style:none;contain:content}[dir=rtl] .md-tabs__list{margin-right:.2rem;margin-left:0}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;font-size:.7rem;-webkit-backface-visibility:hidden;backface-visibility:hidden;opacity:.7;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden] .md-tabs__link{transform:translateY(50%);opacity:0;transition:transform 0ms .1s,opacity .1s}.md-top{position:-webkit-sticky;position:sticky;bottom:.4rem;z-index:1;float:right;margin:-2.8rem .4rem .4rem;padding:.4rem;color:var(--md-primary-bg-color);background:var(--md-primary-fg-color);border-radius:100%;outline:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transform:translateY(0);transition:opacity 125ms,transform 125ms cubic-bezier(.4,0,.2,1),background-color 125ms}[dir=rtl] .md-top{float:left}.md-top[data-md-state=hidden]{transform:translateY(-.2rem);opacity:0}.md-top:focus,.md-top:hover{background:var(--md-accent-fg-color);transform:scale(1.1)}:root{--md-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'><path d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/></svg>")}.md-version{flex-shrink:0;height:2.4rem;font-size:.8rem}.md-version__current{position:relative;top:.05rem;margin-right:.4rem;margin-left:1.4rem}[dir=rtl] .md-version__current{margin-right:1.4rem;margin-left:.4rem}.md-version__current:after{display:inline-block;width:.4rem;height:.6rem;margin-left:.4rem;background-color:currentColor;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;content:""}[dir=rtl] .md-version__current:after{margin-right:.4rem;margin-left:0}.md-version__list{position:absolute;top:.15rem;z-index:1;max-height:1.8rem;margin:.2rem .8rem;padding:0;overflow:auto;color:var(--md-default-fg-color);list-style-type:none;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);opacity:0;transition:max-height 0ms .5s,opacity .25s .25s;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory}.md-version__list:focus-within,.md-version__list:hover{max-height:10rem;opacity:1;transition:max-height .25s,opacity .25s}.md-version__item{line-height:1.8rem}.md-version__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;white-space:nowrap;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}[dir=rtl] .md-version__link{padding-right:.6rem;padding-left:1.2rem}.md-version__link:focus,.md-version__link:hover{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");--md-admonition-icon--abstract:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z'/></svg>");--md-admonition-icon--info:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");--md-admonition-icon--tip:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");--md-admonition-icon--success:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/></svg>");--md-admonition-icon--question:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");--md-admonition-icon--warning:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");--md-admonition-icon--failure:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z'/></svg>");--md-admonition-icon--danger:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m11.5 20 4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z'/></svg>");--md-admonition-icon--bug:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");--md-admonition-icon--example:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");--md-admonition-icon--quote:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>")}.md-typeset .admonition,.md-typeset details{margin:1.5625em 0;padding:0 .6rem;overflow:hidden;color:var(--md-admonition-fg-color);font-size:.64rem;page-break-inside:avoid;background-color:var(--md-admonition-bg-color);border-left:.2rem solid #448aff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 .025rem .05rem rgba(0,0,0,.05)}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-top:1em;margin-bottom:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition-title,.md-typeset summary{position:relative;margin:0 -.6rem 0 -.8rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(68,138,255,.1);border-left:.2rem solid #448aff}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{margin:0 -.8rem 0 -.6rem;padding:.4rem 2rem .4rem .6rem;border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}.md-typeset .admonition-title:before,.md-typeset summary:before{position:absolute;left:.6rem;width:1rem;height:1rem;background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem;left:auto}.md-typeset .admonition-title+.tabbed-set:last-child,.md-typeset summary+.tabbed-set:last-child{margin-top:0}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:rgba(68,138,255,.1);border-color:#448aff}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-color:#00b0ff}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary,.md-typeset .summary>.admonition-title,.md-typeset .summary>summary,.md-typeset .tldr>.admonition-title,.md-typeset .tldr>summary{background-color:rgba(0,176,255,.1);border-color:#00b0ff}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before,.md-typeset .summary>.admonition-title:before,.md-typeset .summary>summary:before,.md-typeset .tldr>.admonition-title:before,.md-typeset .tldr>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-color:#00b8d4}.md-typeset .info>.admonition-title,.md-typeset .info>summary,.md-typeset .todo>.admonition-title,.md-typeset .todo>summary{background-color:rgba(0,184,212,.1);border-color:#00b8d4}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before,.md-typeset .todo>.admonition-title:before,.md-typeset .todo>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .hint>.admonition-title,.md-typeset .hint>summary,.md-typeset .important>.admonition-title,.md-typeset .important>summary,.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:rgba(0,191,165,.1);border-color:#00bfa5}.md-typeset .hint>.admonition-title:before,.md-typeset .hint>summary:before,.md-typeset .important>.admonition-title:before,.md-typeset .important>summary:before,.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-color:#00c853}.md-typeset .check>.admonition-title,.md-typeset .check>summary,.md-typeset .done>.admonition-title,.md-typeset .done>summary,.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:rgba(0,200,83,.1);border-color:#00c853}.md-typeset .check>.admonition-title:before,.md-typeset .check>summary:before,.md-typeset .done>.admonition-title:before,.md-typeset .done>summary:before,.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-color:#64dd17}.md-typeset .faq>.admonition-title,.md-typeset .faq>summary,.md-typeset .help>.admonition-title,.md-typeset .help>summary,.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:rgba(100,221,23,.1);border-color:#64dd17}.md-typeset .faq>.admonition-title:before,.md-typeset .faq>summary:before,.md-typeset .help>.admonition-title:before,.md-typeset .help>summary:before,.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-color:#ff9100}.md-typeset .attention>.admonition-title,.md-typeset .attention>summary,.md-typeset .caution>.admonition-title,.md-typeset .caution>summary,.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:rgba(255,145,0,.1);border-color:#ff9100}.md-typeset .attention>.admonition-title:before,.md-typeset .attention>summary:before,.md-typeset .caution>.admonition-title:before,.md-typeset .caution>summary:before,.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-color:#ff5252}.md-typeset .fail>.admonition-title,.md-typeset .fail>summary,.md-typeset .failure>.admonition-title,.md-typeset .failure>summary,.md-typeset .missing>.admonition-title,.md-typeset .missing>summary{background-color:rgba(255,82,82,.1);border-color:#ff5252}.md-typeset .fail>.admonition-title:before,.md-typeset .fail>summary:before,.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before,.md-typeset .missing>.admonition-title:before,.md-typeset .missing>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-color:#ff1744}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary,.md-typeset .error>.admonition-title,.md-typeset .error>summary{background-color:rgba(255,23,68,.1);border-color:#ff1744}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before,.md-typeset .error>.admonition-title:before,.md-typeset .error>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:rgba(245,0,87,.1);border-color:#f50057}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:rgba(124,77,255,.1);border-color:#7c4dff}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .cite>.admonition-title,.md-typeset .cite>summary,.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:hsla(0,0%,62%,.1);border-color:#9e9e9e}.md-typeset .cite>.admonition-title:before,.md-typeset .cite>summary:before,.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z'/></svg>")}.md-typeset [id^="fnref:"]:target{scroll-margin-top:0;margin-top:-3.4rem;padding-top:3.4rem}.md-typeset [id^="fn:"]:target{scroll-margin-top:0;margin-top:-3.45rem;padding-top:3.45rem}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}.md-typeset .footnote>ol{margin-left:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{transform:translateX(0);opacity:1}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-backref{display:inline-block;color:var(--md-typeset-a-color);font-size:0;vertical-align:text-bottom;transform:translateX(.25rem);opacity:0;transition:color .25s,transform .25s .25s,opacity 125ms .25s}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);transform:translateX(0);opacity:1}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{display:inline-block;width:.8rem;height:.8rem;background-color:currentColor;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;color:var(--md-default-fg-color--lighter);opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{scroll-margin-top:3.6rem}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{scroll-margin-top:0}.md-typeset h1:target:before,.md-typeset h2:target:before,.md-typeset h3:target:before{display:block;margin-top:-3.4rem;padding-top:3.4rem;content:""}.md-typeset h4:target{scroll-margin-top:0}.md-typeset h4:target:before{display:block;margin-top:-3.45rem;padding-top:3.45rem;content:""}.md-typeset h5:target,.md-typeset h6:target{scroll-margin-top:0}.md-typeset h5:target:before,.md-typeset h6:target:before{display:block;margin-top:-3.6rem;padding-top:3.6rem;content:""}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{width:-webkit-min-content;width:-moz-min-content;width:min-content;margin:1em auto!important;padding:0 .8rem;touch-action:auto}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;overflow:auto;box-shadow:none}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>")}.md-typeset details{display:flow-root;padding-top:0;overflow:visible}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){padding-bottom:0;box-shadow:none}.md-typeset details:not([open])>summary{border-radius:.1rem}.md-typeset details:after{display:table;content:""}.md-typeset summary{display:block;min-height:1rem;padding:.4rem 1.8rem .4rem 2rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;cursor:pointer}[dir=rtl] .md-typeset summary{padding:.4rem 2.2rem .4rem 1.8rem}.md-typeset summary:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset summary:after{position:absolute;top:.4rem;right:.4rem;width:1rem;height:1rem;background-color:currentColor;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transform:rotate(0deg);transition:transform .25s;content:""}[dir=rtl] .md-typeset summary:after{right:auto;left:.4rem;transform:rotate(180deg)}.md-typeset summary::-webkit-details-marker,.md-typeset summary::marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{width:1.125em;max-height:100%;fill:currentColor}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{margin:0 -.125em;padding:0 .125em;border-radius:.1rem}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em;background-color:var(--md-code-hl-color)}.highlight [data-linenos]:before{position:-webkit-sticky;position:sticky;left:-1.1764705882em;float:left;margin-right:1.1764705882em;margin-left:-1.1764705882em;padding-left:1.1764705882em;color:var(--md-default-fg-color--light);background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable{display:flow-root;overflow:hidden}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable .linenos{padding:.7720588235em 0 .7720588235em 1.1764705882em;font-size:.85em;background-color:var(--md-code-bg-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{padding-right:.5882352941em;box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;overflow:hidden}.md-typeset .highlighttable{margin:1em 0;direction:ltr;border-radius:.1rem}.md-typeset .highlighttable code{border-radius:0}@media screen and (max-width:44.9375em){.md-typeset>.highlight{margin:1em -.8rem}.md-typeset>.highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.highlight code{border-radius:0}.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.highlighttable .hll{margin:0 -.8rem;padding:0 .8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{position:relative;margin:0;color:inherit;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.md-typeset .keys span{padding:0 .2em;color:var(--md-default-fg-color--light)}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{padding-right:.4em;content:"⎇"}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{padding-right:.4em;content:"⌘"}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{padding-right:.4em;content:"⌃"}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{padding-right:.4em;content:"◆"}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{padding-right:.4em;content:"⌥"}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{padding-right:.4em;content:"⇧"}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{padding-right:.4em;content:"❖"}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{padding-right:.4em;content:"⊞"}.md-typeset .keys .key-arrow-down:before{padding-right:.4em;content:"↓"}.md-typeset .keys .key-arrow-left:before{padding-right:.4em;content:"←"}.md-typeset .keys .key-arrow-right:before{padding-right:.4em;content:"→"}.md-typeset .keys .key-arrow-up:before{padding-right:.4em;content:"↑"}.md-typeset .keys .key-backspace:before{padding-right:.4em;content:"⌫"}.md-typeset .keys .key-backtab:before{padding-right:.4em;content:"⇤"}.md-typeset .keys .key-caps-lock:before{padding-right:.4em;content:"⇪"}.md-typeset .keys .key-clear:before{padding-right:.4em;content:"⌧"}.md-typeset .keys .key-context-menu:before{padding-right:.4em;content:"☰"}.md-typeset .keys .key-delete:before{padding-right:.4em;content:"⌦"}.md-typeset .keys .key-eject:before{padding-right:.4em;content:"⏏"}.md-typeset .keys .key-end:before{padding-right:.4em;content:"⤓"}.md-typeset .keys .key-escape:before{padding-right:.4em;content:"⎋"}.md-typeset .keys .key-home:before{padding-right:.4em;content:"⤒"}.md-typeset .keys .key-insert:before{padding-right:.4em;content:"⎀"}.md-typeset .keys .key-page-down:before{padding-right:.4em;content:"⇟"}.md-typeset .keys .key-page-up:before{padding-right:.4em;content:"⇞"}.md-typeset .keys .key-print-screen:before{padding-right:.4em;content:"⎙"}.md-typeset .keys .key-tab:after{padding-left:.4em;content:"⇥"}.md-typeset .keys .key-num-enter:after{padding-left:.4em;content:"⌤"}.md-typeset .keys .key-enter:after{padding-left:.4em;content:"⏎"}.md-typeset .tabbed-content{display:none;order:99;width:100%;box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}@media print{.md-typeset .tabbed-content{display:block;order:0}}.md-typeset .tabbed-content>.highlight:only-child pre,.md-typeset .tabbed-content>.highlighttable:only-child,.md-typeset .tabbed-content>pre:only-child{margin:0}.md-typeset .tabbed-content>.highlight:only-child pre>code,.md-typeset .tabbed-content>.highlighttable:only-child>code,.md-typeset .tabbed-content>pre:only-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-content>.tabbed-set{margin:0}.md-typeset .tabbed-set{position:relative;display:flex;flex-wrap:wrap;margin:1em 0;border-radius:.1rem}.md-typeset .tabbed-set>input{position:absolute;width:0;height:0;opacity:0}.md-typeset .tabbed-set>input:checked+label{color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:checked+label+.tabbed-content{display:block}.md-typeset .tabbed-set>input:focus+label{outline-style:auto}.md-typeset .tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.md-typeset .tabbed-set>label{z-index:1;width:auto;padding:.9375em 1.25em .78125em;color:var(--md-default-fg-color--light);font-weight:700;font-size:.64rem;border-bottom:.1rem solid transparent;cursor:pointer;transition:color .25s}.md-typeset .tabbed-set>label:hover{color:var(--md-accent-fg-color)}:root{--md-tasklist-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>");--md-tasklist-icon--checked:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>")}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control [type=checkbox]{z-index:-1;opacity:0}.md-typeset .task-list-indicator:before{position:absolute;top:.15em;left:-1.5em;width:1.25em;height:1.25em;background-color:var(--md-default-fg-color--lightest);-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em;left:auto}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media screen and (min-width:45em){.md-typeset .inline{float:left;width:11.7rem;margin-top:0;margin-right:.8rem;margin-bottom:.8rem}.md-typeset .inline.end,[dir=rtl] .md-typeset .inline{float:right;margin-right:0;margin-left:.8rem}[dir=rtl] .md-typeset .inline.end{float:left;margin-right:.8rem;margin-left:0}}
+/*# sourceMappingURL=main.33e2939f.min.css.map */
\ No newline at end of file
diff --git a/latest/assets/stylesheets/main.33e2939f.min.css.map b/latest/assets/stylesheets/main.33e2939f.min.css.map
new file mode 100644 (file)
index 0000000..2b31341
--- /dev/null
@@ -0,0 +1 @@
+{"version":3,"sources":["src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_reset.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","node_modules/material-shadows/material-shadows.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_announce.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_top.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/main.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Keyboard key\n  .keys {\n\n    // Keyboard key icon\n    kbd::before,\n    kbd::after {\n      position: relative;\n      margin: 0;\n      color: inherit;\n      -moz-osx-font-smoothing: initial;\n      -webkit-font-smoothing: initial;\n    }\n\n    // Surrounding text\n    span {\n      padding: 0 px2em(3.2px);\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Define keyboard keys with left icon\n    @each $name, $code in (\n\n      // Modifiers\n      \"alt\":           \"\\2387\",\n      \"left-alt\":      \"\\2387\",\n      \"right-alt\":     \"\\2387\",\n      \"command\":       \"\\2318\",\n      \"left-command\":  \"\\2318\",\n      \"right-command\": \"\\2318\",\n      \"control\":       \"\\2303\",\n      \"left-control\":  \"\\2303\",\n      \"right-control\": \"\\2303\",\n      \"meta\":          \"\\25C6\",\n      \"left-meta\":     \"\\25C6\",\n      \"right-meta\":    \"\\25C6\",\n      \"option\":        \"\\2325\",\n      \"left-option\":   \"\\2325\",\n      \"right-option\":  \"\\2325\",\n      \"shift\":         \"\\21E7\",\n      \"left-shift\":    \"\\21E7\",\n      \"right-shift\":   \"\\21E7\",\n      \"super\":         \"\\2756\",\n      \"left-super\":    \"\\2756\",\n      \"right-super\":   \"\\2756\",\n      \"windows\":       \"\\229E\",\n      \"left-windows\":  \"\\229E\",\n      \"right-windows\": \"\\229E\",\n\n      // Other keys\n      \"arrow-down\":    \"\\2193\",\n      \"arrow-left\":    \"\\2190\",\n      \"arrow-right\":   \"\\2192\",\n      \"arrow-up\":      \"\\2191\",\n      \"backspace\":     \"\\232B\",\n      \"backtab\":       \"\\21E4\",\n      \"caps-lock\":     \"\\21EA\",\n      \"clear\":         \"\\2327\",\n      \"context-menu\":  \"\\2630\",\n      \"delete\":        \"\\2326\",\n      \"eject\":         \"\\23CF\",\n      \"end\":           \"\\2913\",\n      \"escape\":        \"\\238B\",\n      \"home\":          \"\\2912\",\n      \"insert\":        \"\\2380\",\n      \"page-down\":     \"\\21DF\",\n      \"page-up\":       \"\\21DE\",\n      \"print-screen\":  \"\\2399\"\n    ) {\n      .key-#{$name} {\n        &::before {\n          padding-right: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n\n    // Define keyboard keys with right icon\n    @each $name, $code in (\n      \"tab\":           \"\\21E5\",\n      \"num-enter\":     \"\\2324\",\n      \"enter\":         \"\\23CE\"\n    ) {\n      .key-#{$name} {\n        &::after {\n          padding-left: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n  }\n}\n","@charset \"UTF-8\";\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n}\n\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\na {\n  color: inherit;\n  text-decoration: none;\n}\n\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: 0.05rem;\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  line-height: 1em;\n}\n\nimg {\n  border-style: none;\n}\n\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\ninput {\n  border: 0;\n  outline: none;\n}\n\n:root {\n  --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color: hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n:root > * {\n  --md-code-fg-color: hsla(200, 18%, 26%, 1);\n  --md-code-bg-color: hsla(0, 0%, 96%, 1);\n  --md-code-hl-color: hsla(60, 100%, 50%, 0.5);\n  --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n  --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n  --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n  --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n  --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n  --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n  --md-code-hl-name-color: var(--md-code-fg-color);\n  --md-code-hl-operator-color: var(--md-default-fg-color--light);\n  --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n  --md-code-hl-comment-color: var(--md-default-fg-color--light);\n  --md-code-hl-generic-color: var(--md-default-fg-color--light);\n  --md-code-hl-variable-color: var(--md-default-fg-color--light);\n  --md-typeset-color: var(--md-default-fg-color);\n  --md-typeset-a-color: var(--md-primary-fg-color);\n  --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5);\n  --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n  --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n  --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n  --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n  --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n  --md-admonition-fg-color: var(--md-default-fg-color);\n  --md-admonition-bg-color: var(--md-default-bg-color);\n  --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n  --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n}\n\n.md-icon svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family: var(--md-text-font-family, _), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family: var(--md-code-font-family, _), SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n.md-typeset {\n  font-size: 0.8rem;\n  line-height: 1.6;\n  color-adjust: exact;\n}\n@media print {\n  .md-typeset {\n    font-size: 0.68rem;\n  }\n}\n.md-typeset ul,\n.md-typeset ol,\n.md-typeset dl,\n.md-typeset figure,\n.md-typeset blockquote,\n.md-typeset pre {\n  display: flow-root;\n  margin: 1em 0;\n}\n.md-typeset h1 {\n  margin: 0 0 1.25em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 300;\n  font-size: 2em;\n  line-height: 1.3;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 {\n  margin: 1.6em 0 0.64em;\n  font-weight: 300;\n  font-size: 1.5625em;\n  line-height: 1.4;\n  letter-spacing: -0.01em;\n}\n.md-typeset h3 {\n  margin: 1.6em 0 0.8em;\n  font-weight: 400;\n  font-size: 1.25em;\n  line-height: 1.5;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 + h3 {\n  margin-top: 0.8em;\n}\n.md-typeset h4 {\n  margin: 1em 0;\n  font-weight: 700;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5,\n.md-typeset h6 {\n  margin: 1.25em 0;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.8em;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5 {\n  text-transform: uppercase;\n}\n.md-typeset hr {\n  display: flow-root;\n  margin: 1.5em 0;\n  border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset a {\n  color: var(--md-typeset-a-color);\n  word-break: break-word;\n}\n.md-typeset a, .md-typeset a::before {\n  transition: color 125ms;\n}\n.md-typeset a:focus, .md-typeset a:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n  color: var(--md-code-fg-color);\n  direction: ltr;\n}\n@media print {\n  .md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n    white-space: pre-wrap;\n  }\n}\n.md-typeset code {\n  padding: 0 0.2941176471em;\n  font-size: 0.85em;\n  word-break: break-word;\n  background-color: var(--md-code-bg-color);\n  border-radius: 0.1rem;\n  box-decoration-break: clone;\n}\n.md-typeset code:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset h1 code,\n.md-typeset h2 code,\n.md-typeset h3 code,\n.md-typeset h4 code,\n.md-typeset h5 code,\n.md-typeset h6 code {\n  margin: initial;\n  padding: initial;\n  background-color: transparent;\n  box-shadow: none;\n}\n.md-typeset a code {\n  color: currentColor;\n}\n.md-typeset pre {\n  position: relative;\n  line-height: 1.4;\n}\n.md-typeset pre > code {\n  display: block;\n  margin: 0;\n  padding: 0.7720588235em 1.1764705882em;\n  overflow: auto;\n  word-break: normal;\n  box-shadow: none;\n  box-decoration-break: slice;\n  touch-action: auto;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-typeset pre > code:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-typeset pre > code::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > pre {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > pre code {\n    border-radius: 0;\n  }\n}\n.md-typeset kbd {\n  display: inline-block;\n  padding: 0 0.6666666667em;\n  color: var(--md-default-fg-color);\n  font-size: 0.75em;\n  vertical-align: text-top;\n  word-break: break-word;\n  background-color: var(--md-typeset-kbd-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.1rem 0 0.05rem var(--md-typeset-kbd-border-color), 0 0.1rem 0 var(--md-typeset-kbd-border-color), 0 -0.1rem 0.2rem var(--md-typeset-kbd-accent-color) inset;\n}\n.md-typeset mark {\n  color: inherit;\n  word-break: break-word;\n  background-color: var(--md-typeset-mark-color);\n  box-decoration-break: clone;\n}\n.md-typeset abbr {\n  text-decoration: none;\n  border-bottom: 0.05rem dotted var(--md-default-fg-color--light);\n  cursor: help;\n}\n@media (hover: none) {\n  .md-typeset abbr {\n    position: relative;\n  }\n  .md-typeset abbr[title]:focus::after, .md-typeset abbr[title]:hover::after {\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n    position: absolute;\n    left: 0;\n    display: inline-block;\n    width: auto;\n    min-width: max-content;\n    max-width: 80%;\n    margin-top: 2em;\n    padding: 0.2rem 0.3rem;\n    color: var(--md-default-bg-color);\n    font-size: 0.7rem;\n    background-color: var(--md-default-fg-color);\n    border-radius: 0.1rem;\n    content: attr(title);\n  }\n}\n.md-typeset small {\n  opacity: 0.75;\n}\n.md-typeset sup,\n.md-typeset sub {\n  margin-left: 0.078125em;\n}\n[dir=rtl] .md-typeset sup,\n[dir=rtl] .md-typeset sub {\n  margin-right: 0.078125em;\n  margin-left: initial;\n}\n.md-typeset blockquote {\n  padding-left: 0.6rem;\n  color: var(--md-default-fg-color--light);\n  border-left: 0.2rem solid var(--md-default-fg-color--lighter);\n}\n[dir=rtl] .md-typeset blockquote {\n  padding-right: 0.6rem;\n  padding-left: initial;\n  border-right: 0.2rem solid var(--md-default-fg-color--lighter);\n  border-left: initial;\n}\n.md-typeset ul {\n  list-style-type: disc;\n}\n.md-typeset ul,\n.md-typeset ol {\n  margin-left: 0.625em;\n  padding: 0;\n}\n[dir=rtl] .md-typeset ul,\n[dir=rtl] .md-typeset ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset ul ol,\n.md-typeset ol ol {\n  list-style-type: lower-alpha;\n}\n.md-typeset ul ol ol,\n.md-typeset ol ol ol {\n  list-style-type: lower-roman;\n}\n.md-typeset ul li,\n.md-typeset ol li {\n  margin-bottom: 0.5em;\n  margin-left: 1.25em;\n}\n[dir=rtl] .md-typeset ul li,\n[dir=rtl] .md-typeset ol li {\n  margin-right: 1.25em;\n  margin-left: initial;\n}\n.md-typeset ul li p,\n.md-typeset ul li blockquote,\n.md-typeset ol li p,\n.md-typeset ol li blockquote {\n  margin: 0.5em 0;\n}\n.md-typeset ul li:last-child,\n.md-typeset ol li:last-child {\n  margin-bottom: 0;\n}\n.md-typeset ul li ul,\n.md-typeset ul li ol,\n.md-typeset ol li ul,\n.md-typeset ol li ol {\n  margin: 0.5em 0 0.5em 0.625em;\n}\n[dir=rtl] .md-typeset ul li ul,\n[dir=rtl] .md-typeset ul li ol,\n[dir=rtl] .md-typeset ol li ul,\n[dir=rtl] .md-typeset ol li ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset dd {\n  margin: 1em 0 1.5em 1.875em;\n}\n[dir=rtl] .md-typeset dd {\n  margin-right: 1.875em;\n  margin-left: initial;\n}\n.md-typeset img,\n.md-typeset svg {\n  max-width: 100%;\n  height: auto;\n}\n.md-typeset img[align=left],\n.md-typeset svg[align=left] {\n  margin: 1em;\n  margin-left: 0;\n}\n.md-typeset img[align=right],\n.md-typeset svg[align=right] {\n  margin: 1em;\n  margin-right: 0;\n}\n.md-typeset img[align]:only-child,\n.md-typeset svg[align]:only-child {\n  margin-top: 0;\n}\n.md-typeset figure {\n  width: fit-content;\n  max-width: 100%;\n  margin: 0 auto;\n  text-align: center;\n}\n.md-typeset figure img {\n  display: block;\n}\n.md-typeset figcaption {\n  max-width: 24rem;\n  margin: 1em auto 2em;\n  font-style: italic;\n}\n.md-typeset iframe {\n  max-width: 100%;\n}\n.md-typeset table:not([class]) {\n  display: inline-block;\n  max-width: 100%;\n  overflow: auto;\n  font-size: 0.64rem;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);\n  touch-action: auto;\n}\n@media print {\n  .md-typeset table:not([class]) {\n    display: table;\n  }\n}\n.md-typeset table:not([class]) + * {\n  margin-top: 1.5em;\n}\n.md-typeset table:not([class]) th > *:first-child,\n.md-typeset table:not([class]) td > *:first-child {\n  margin-top: 0;\n}\n.md-typeset table:not([class]) th > *:last-child,\n.md-typeset table:not([class]) td > *:last-child {\n  margin-bottom: 0;\n}\n.md-typeset table:not([class]) th:not([align]),\n.md-typeset table:not([class]) td:not([align]) {\n  text-align: left;\n}\n[dir=rtl] .md-typeset table:not([class]) th:not([align]),\n[dir=rtl] .md-typeset table:not([class]) td:not([align]) {\n  text-align: right;\n}\n.md-typeset table:not([class]) th {\n  min-width: 5rem;\n  padding: 0.9375em 1.25em;\n  color: var(--md-default-bg-color);\n  vertical-align: top;\n  background-color: var(--md-default-fg-color--light);\n}\n.md-typeset table:not([class]) th a {\n  color: inherit;\n}\n.md-typeset table:not([class]) td {\n  padding: 0.9375em 1.25em;\n  vertical-align: top;\n  border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset table:not([class]) tr {\n  transition: background-color 125ms;\n}\n.md-typeset table:not([class]) tr:hover {\n  background-color: rgba(0, 0, 0, 0.035);\n  box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;\n}\n.md-typeset table:not([class]) tr:first-child td {\n  border-top: 0;\n}\n.md-typeset table:not([class]) a {\n  word-break: normal;\n}\n.md-typeset table th[role=columnheader] {\n  cursor: pointer;\n}\n.md-typeset table th[role=columnheader]::after {\n  display: inline-block;\n  width: 1.2em;\n  height: 1.2em;\n  margin-left: 0.5em;\n  vertical-align: sub;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-typeset table th[role=columnheader][aria-sort=ascending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--ascending);\n}\n.md-typeset table th[role=columnheader][aria-sort=descending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--descending);\n}\n.md-typeset__scrollwrap {\n  margin: 1em -0.8rem;\n  overflow-x: auto;\n  touch-action: auto;\n}\n.md-typeset__table {\n  display: inline-block;\n  margin-bottom: 0.5em;\n  padding: 0 0.8rem;\n}\n@media print {\n  .md-typeset__table {\n    display: block;\n  }\n}\nhtml .md-typeset__table table {\n  display: table;\n  width: 100%;\n  margin: 0;\n  overflow: hidden;\n}\n\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  font-size: 125%;\n}\n@media screen and (min-width: 100em) {\n  html {\n    font-size: 137.5%;\n  }\n}\n@media screen and (min-width: 125em) {\n  html {\n    font-size: 150%;\n  }\n}\n\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  font-size: 0.5rem;\n  background-color: var(--md-default-bg-color);\n}\n@media print {\n  body {\n    display: block;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  body[data-md-state=lock] {\n    position: fixed;\n  }\n}\n\n.md-grid {\n  max-width: 61rem;\n  margin-right: auto;\n  margin-left: auto;\n}\n\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n}\n@media print {\n  .md-container {\n    display: block;\n  }\n}\n\n.md-main {\n  flex-grow: 1;\n}\n.md-main__inner {\n  display: flex;\n  height: 100%;\n  margin-top: 1.5rem;\n}\n\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.md-toggle {\n  display: none;\n}\n\n.md-option {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-option:checked + label:not([hidden]) {\n  display: block;\n}\n.md-option.focus-visible + label {\n  outline-style: auto;\n}\n\n.md-skip {\n  position: fixed;\n  z-index: -1;\n  margin: 0.5rem;\n  padding: 0.3rem 0.5rem;\n  color: var(--md-default-bg-color);\n  font-size: 0.64rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(0.4rem);\n  opacity: 0;\n}\n.md-skip:focus {\n  z-index: 10;\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 175ms 75ms;\n}\n\n@page {\n  margin: 25mm;\n}\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-announce {\n    display: none;\n  }\n}\n.md-announce__inner {\n  margin: 0.6rem auto;\n  padding: 0 0.8rem;\n  color: var(--md-footer-fg-color);\n  font-size: 0.7rem;\n}\n\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n.md-clipboard {\n  position: absolute;\n  top: 0.5em;\n  right: 0.5em;\n  z-index: 1;\n  width: 1.5em;\n  height: 1.5em;\n  color: var(--md-default-fg-color--lightest);\n  border-radius: 0.1rem;\n  cursor: pointer;\n  transition: color 250ms;\n}\n@media print {\n  .md-clipboard {\n    display: none;\n  }\n}\n.md-clipboard:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n:hover > .md-clipboard {\n  color: var(--md-default-fg-color--light);\n}\n.md-clipboard:focus, .md-clipboard:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-clipboard::after {\n  display: block;\n  width: 1.125em;\n  height: 1.125em;\n  margin: 0 auto;\n  background-color: currentColor;\n  mask-image: var(--md-clipboard-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-clipboard--inline {\n  cursor: pointer;\n}\n.md-clipboard--inline code {\n  transition: color 250ms, background-color 250ms;\n}\n.md-clipboard--inline:focus code, .md-clipboard--inline:hover code {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n\n.md-content {\n  flex-grow: 1;\n  overflow: hidden;\n  scroll-padding-top: 51.2rem;\n}\n.md-content__inner {\n  margin: 0 0.8rem 1.2rem;\n  padding-top: 0.6rem;\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-left: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n    margin-left: 0.8rem;\n  }\n  .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 0.8rem;\n    margin-left: 1.2rem;\n  }\n}\n.md-content__inner::before {\n  display: block;\n  height: 0.4rem;\n  content: \"\";\n}\n.md-content__inner > :last-child {\n  margin-bottom: 0;\n}\n.md-content__button {\n  float: right;\n  margin: 0.4rem 0;\n  margin-left: 0.4rem;\n  padding: 0;\n}\n@media print {\n  .md-content__button {\n    display: none;\n  }\n}\n[dir=rtl] .md-content__button {\n  float: left;\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n[dir=rtl] .md-content__button svg {\n  transform: scaleX(-1);\n}\n.md-typeset .md-content__button {\n  color: var(--md-default-fg-color--lighter);\n}\n.md-content__button svg {\n  display: inline;\n  vertical-align: top;\n}\n\n.md-dialog {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n  position: fixed;\n  right: 0.8rem;\n  bottom: 0.8rem;\n  left: initial;\n  z-index: 2;\n  min-width: 11.1rem;\n  padding: 0.4rem 0.6rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(100%);\n  opacity: 0;\n  transition: transform 0ms 400ms, opacity 400ms;\n  pointer-events: none;\n}\n@media print {\n  .md-dialog {\n    display: none;\n  }\n}\n[dir=rtl] .md-dialog {\n  right: initial;\n  left: 0.8rem;\n}\n.md-dialog[data-md-state=open] {\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1), opacity 400ms;\n  pointer-events: initial;\n}\n.md-dialog__inner {\n  color: var(--md-default-bg-color);\n  font-size: 0.7rem;\n}\n\n.md-typeset .md-button {\n  display: inline-block;\n  padding: 0.625em 2em;\n  color: var(--md-primary-fg-color);\n  font-weight: 700;\n  border: 0.1rem solid currentColor;\n  border-radius: 0.1rem;\n  transition: color 125ms, background-color 125ms, border-color 125ms;\n}\n.md-typeset .md-button--primary {\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  border-color: var(--md-primary-fg-color);\n}\n.md-typeset .md-button:focus, .md-typeset .md-button:hover {\n  color: var(--md-accent-bg-color);\n  background-color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .md-input {\n  height: 1.8rem;\n  padding: 0 0.6rem;\n  font-size: 0.8rem;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transition: box-shadow 250ms;\n}\n.md-typeset .md-input:focus, .md-typeset .md-input:hover {\n  box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.15), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.15);\n}\n.md-typeset .md-input--stretch {\n  width: 100%;\n}\n\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0), 0 0.2rem 0.4rem rgba(0, 0, 0, 0);\n}\n@media print {\n  .md-header {\n    display: none;\n  }\n}\n.md-header[data-md-state=shadow] {\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), box-shadow 250ms;\n}\n.md-header[data-md-state=hidden] {\n  transform: translateY(-100%);\n  transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), box-shadow 250ms;\n}\n.md-header .focus-visible {\n  outline-color: currentColor;\n}\n.md-header__inner {\n  display: flex;\n  align-items: center;\n  padding: 0 0.2rem;\n}\n.md-header__button {\n  position: relative;\n  z-index: 1;\n  margin: 0.2rem;\n  padding: 0.4rem;\n  color: currentColor;\n  vertical-align: middle;\n  cursor: pointer;\n  transition: opacity 250ms;\n}\n.md-header__button:hover {\n  opacity: 0.7;\n}\n.md-header__button:not([hidden]) {\n  display: inline-block;\n}\n.md-header__button:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-header__button.md-logo {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-header__button.md-logo {\n    display: none;\n  }\n}\n.md-header__button.md-logo img,\n.md-header__button.md-logo svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n@media screen and (min-width: 60em) {\n  .md-header__button[for=__search] {\n    display: none;\n  }\n}\n.no-js .md-header__button[for=__search] {\n  display: none;\n}\n[dir=rtl] .md-header__button[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__button[for=__drawer] {\n    display: none;\n  }\n}\n.md-header__topic {\n  position: absolute;\n  display: flex;\n  max-width: 100%;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n}\n.md-header__topic + .md-header__topic {\n  z-index: -1;\n  transform: translateX(1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__topic + .md-header__topic {\n  transform: translateX(-1.25rem);\n}\n.md-header__title {\n  flex-grow: 1;\n  height: 2.4rem;\n  margin-right: 0.4rem;\n  margin-left: 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-header__title[data-md-state=active] .md-header__topic {\n  z-index: -1;\n  transform: translateX(-1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic {\n  transform: translateX(1.25rem);\n}\n.md-header__title[data-md-state=active] .md-header__topic + .md-header__topic {\n  z-index: 0;\n  transform: translateX(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: initial;\n}\n.md-header__title > .md-header__ellipsis {\n  position: relative;\n  width: 100%;\n  height: 100%;\n}\n.md-header__option {\n  display: flex;\n  flex-shrink: 0;\n  max-width: 100%;\n  white-space: nowrap;\n  transition: max-width 0ms 250ms, opacity 250ms 250ms;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-header__option {\n  max-width: 0;\n  opacity: 0;\n  transition: max-width 0ms, opacity 0ms;\n}\n.md-header__source {\n  display: none;\n}\n@media screen and (min-width: 60em) {\n  .md-header__source {\n    display: block;\n    width: 11.7rem;\n    max-width: 11.7rem;\n    margin-left: 1rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1rem;\n    margin-left: initial;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__source {\n    margin-left: 1.4rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1.4rem;\n  }\n}\n\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-footer {\n    display: none;\n  }\n}\n.md-footer__inner {\n  padding: 0.2rem;\n  overflow: auto;\n}\n.md-footer__link {\n  display: flex;\n  padding-top: 1.4rem;\n  padding-bottom: 0.4rem;\n  transition: opacity 250ms;\n}\n@media screen and (min-width: 45em) {\n  .md-footer__link {\n    width: 50%;\n  }\n}\n.md-footer__link:focus, .md-footer__link:hover {\n  opacity: 0.7;\n}\n.md-footer__link--prev {\n  float: left;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--prev {\n    width: 25%;\n  }\n  .md-footer__link--prev .md-footer__title {\n    display: none;\n  }\n}\n[dir=rtl] .md-footer__link--prev {\n  float: right;\n}\n[dir=rtl] .md-footer__link--prev svg {\n  transform: scaleX(-1);\n}\n.md-footer__link--next {\n  float: right;\n  text-align: right;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--next {\n    width: 75%;\n  }\n}\n[dir=rtl] .md-footer__link--next {\n  float: left;\n  text-align: left;\n}\n[dir=rtl] .md-footer__link--next svg {\n  transform: scaleX(-1);\n}\n.md-footer__title {\n  position: relative;\n  flex-grow: 1;\n  max-width: calc(100% - 2.4rem);\n  padding: 0 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-footer__button {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n.md-footer__direction {\n  position: absolute;\n  right: 0;\n  left: 0;\n  margin-top: -1rem;\n  padding: 0 1rem;\n  font-size: 0.64rem;\n  opacity: 0.7;\n}\n\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n}\n.md-footer-meta__inner {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  padding: 0.2rem;\n}\nhtml .md-footer-meta.md-typeset a {\n  color: var(--md-footer-fg-color--light);\n}\nhtml .md-footer-meta.md-typeset a:focus, html .md-footer-meta.md-typeset a:hover {\n  color: var(--md-footer-fg-color);\n}\n\n.md-footer-copyright {\n  width: 100%;\n  margin: auto 0.6rem;\n  padding: 0.4rem 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: 0.64rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-copyright {\n    width: auto;\n  }\n}\n.md-footer-copyright__highlight {\n  color: var(--md-footer-fg-color--light);\n}\n\n.md-footer-social {\n  margin: 0 0.4rem;\n  padding: 0.2rem 0 0.6rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-social {\n    padding: 0.6rem 0;\n  }\n}\n.md-footer-social__link {\n  display: inline-block;\n  width: 1.6rem;\n  height: 1.6rem;\n  text-align: center;\n}\n.md-footer-social__link::before {\n  line-height: 1.9;\n}\n.md-footer-social__link svg {\n  max-height: 0.8rem;\n  vertical-align: -25%;\n  fill: currentColor;\n}\n\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n.md-nav {\n  font-size: 0.7rem;\n  line-height: 1.3;\n}\n.md-nav__title {\n  display: block;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n}\n.md-nav__title .md-nav__button {\n  display: none;\n}\n.md-nav__title .md-nav__button img {\n  width: auto;\n  height: 100%;\n}\n.md-nav__title .md-nav__button.md-logo img,\n.md-nav__title .md-nav__button.md-logo svg {\n  display: block;\n  width: 2.4rem;\n  height: 2.4rem;\n  fill: currentColor;\n}\n.md-nav__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-nav__item {\n  padding: 0 0.6rem;\n}\n.md-nav__item .md-nav__item {\n  padding-right: 0;\n}\n[dir=rtl] .md-nav__item .md-nav__item {\n  padding-right: 0.6rem;\n  padding-left: 0;\n}\n.md-nav__link {\n  display: block;\n  margin-top: 0.625em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  cursor: pointer;\n  transition: color 125ms;\n  scroll-snap-align: start;\n}\n.md-nav__link[data-md-state=blur] {\n  color: var(--md-default-fg-color--light);\n}\n.md-nav__item .md-nav__link--active {\n  color: var(--md-typeset-a-color);\n}\n.md-nav__item--nested > .md-nav__link {\n  color: inherit;\n}\n.md-nav__link:focus, .md-nav__link:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-nav--primary .md-nav__link[for=__toc] {\n  display: none;\n}\n.md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n  display: block;\n  width: 100%;\n  height: 100%;\n  mask-image: var(--md-toc-icon);\n  background-color: currentColor;\n}\n.md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n  display: none;\n}\n.md-nav__source {\n  display: none;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-nav--primary, .md-nav--primary .md-nav {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: 1;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n  }\n  .md-nav--primary .md-nav__title,\n.md-nav--primary .md-nav__item {\n    font-size: 0.8rem;\n    line-height: 1.5;\n  }\n  .md-nav--primary .md-nav__title {\n    position: relative;\n    height: 5.6rem;\n    padding: 3rem 0.8rem 0.2rem;\n    color: var(--md-default-fg-color--light);\n    font-weight: 400;\n    line-height: 2.4rem;\n    white-space: nowrap;\n    background-color: var(--md-default-fg-color--lightest);\n    cursor: pointer;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    position: absolute;\n    top: 0.4rem;\n    left: 0.4rem;\n    display: block;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin: 0.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon {\n    right: 0.4rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--prev);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list {\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0.05rem 0 var(--md-default-fg-color--lightest) inset;\n    scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child {\n    border-top: 0;\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color);\n  }\n  .md-nav--primary .md-nav__title .md-logo {\n    position: absolute;\n    top: 0.2rem;\n    left: 0.2rem;\n    display: block;\n    margin: 0.2rem;\n    padding: 0.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-logo {\n    right: 0.2rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__list {\n    flex: 1;\n  }\n  .md-nav--primary .md-nav__item {\n    padding: 0;\n    border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n  }\n  .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link {\n    color: var(--md-typeset-a-color);\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link:focus, .md-nav--primary .md-nav__item--active > .md-nav__link:hover {\n    color: var(--md-accent-fg-color);\n  }\n  .md-nav--primary .md-nav__link {\n    position: relative;\n    margin-top: 0;\n    padding: 0.6rem 0.8rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon {\n    position: absolute;\n    top: 50%;\n    right: 0.6rem;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin-top: -0.6rem;\n    color: inherit;\n    font-size: 1.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon {\n    right: initial;\n    left: 0.6rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  [dir=rtl] .md-nav--primary .md-nav__icon::after {\n    transform: scale(-1);\n  }\n  .md-nav--primary .md-nav--secondary .md-nav__link {\n    position: static;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav {\n    position: static;\n    background-color: transparent;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-left: 1.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-right: 1.4rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-left: 2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-right: 2rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 2.6rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 2.6rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 3.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 3.2rem;\n    padding-left: initial;\n  }\n  .md-nav--secondary {\n    background-color: transparent;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: flex;\n    transform: translateX(100%);\n    opacity: 0;\n    transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), opacity 125ms 50ms;\n  }\n  [dir=rtl] .md-nav__toggle ~ .md-nav {\n    transform: translateX(-100%);\n  }\n  .md-nav__toggle:checked ~ .md-nav {\n    transform: translateX(0);\n    opacity: 1;\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 125ms 125ms;\n  }\n  .md-nav__toggle:checked ~ .md-nav > .md-nav__list {\n    backface-visibility: hidden;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  .md-nav--primary .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n  .md-nav__source {\n    display: block;\n    padding: 0 0.2rem;\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color--dark);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-nav--integrated .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n    scroll-snap-align: initial;\n  }\n  [dir=rtl] .md-nav--integrated .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-nav--secondary .md-nav__title[for=__toc] {\n    scroll-snap-align: start;\n  }\n  .md-nav--secondary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-nav {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    scroll-snap-align: start;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: none;\n  }\n  .md-nav__toggle:checked ~ .md-nav, .md-nav__toggle:indeterminate ~ .md-nav {\n    display: block;\n  }\n  .md-nav__item--nested > .md-nav > .md-nav__title {\n    display: none;\n  }\n  .md-nav__item--section {\n    display: block;\n    margin: 1.25em 0;\n  }\n  .md-nav__item--section:last-child {\n    margin-bottom: 0;\n  }\n  .md-nav__item--section > .md-nav__link {\n    display: none;\n  }\n  .md-nav__item--section > .md-nav {\n    display: block;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__list > .md-nav__item {\n    padding: 0;\n  }\n  .md-nav__icon {\n    float: right;\n    width: 0.9rem;\n    height: 0.9rem;\n    transition: transform 250ms;\n  }\n  [dir=rtl] .md-nav__icon {\n    float: left;\n    transform: rotate(180deg);\n  }\n  .md-nav__icon::after {\n    display: inline-block;\n    width: 100%;\n    height: 100%;\n    vertical-align: -0.1rem;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon, .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link .md-nav__icon {\n    transform: rotate(90deg);\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--nested,\n.md-nav--lifted > .md-nav__title {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active {\n    display: block;\n    padding: 0;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0 0.6rem;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav__item {\n    padding-right: 0.6rem;\n  }\n  .md-nav--lifted .md-nav[data-md-level=\"1\"] {\n    display: block;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: block;\n    margin-bottom: 1.25em;\n    border-left: 0.05rem solid var(--md-primary-fg-color);\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav > .md-nav__title {\n    display: none;\n  }\n}\n\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n.md-search {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search {\n    padding: 0.2rem 0;\n  }\n}\n.no-js .md-search {\n  display: none;\n}\n.md-search__overlay {\n  z-index: 1;\n  opacity: 0;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__overlay {\n    position: absolute;\n    top: 0.2rem;\n    left: -2.2rem;\n    width: 2rem;\n    height: 2rem;\n    overflow: hidden;\n    background-color: var(--md-default-bg-color);\n    border-radius: 1rem;\n    transform-origin: center;\n    transition: transform 300ms 100ms, opacity 200ms 200ms;\n    pointer-events: none;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: -2.2rem;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    opacity: 1;\n    transition: transform 400ms, opacity 100ms;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    cursor: pointer;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: 0;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    width: 100%;\n    height: 200vh;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@media screen and (max-width: 29.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(45);\n  }\n}\n@media screen and (min-width: 30em) and (max-width: 44.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(60);\n  }\n}\n@media screen and (min-width: 45em) and (max-width: 59.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(75);\n  }\n}\n.md-search__inner {\n  backface-visibility: hidden;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__inner {\n    position: fixed;\n    top: 0;\n    left: 100%;\n    z-index: 2;\n    width: 100%;\n    height: 100%;\n    transform: translateX(5%);\n    opacity: 0;\n    transition: right 0ms 300ms, left 0ms 300ms, transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms 150ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    left: 0;\n    transform: translateX(0);\n    opacity: 1;\n    transition: right 0ms 0ms, left 0ms 0ms, transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms 150ms;\n  }\n  [dir=rtl] [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    right: 0;\n    left: initial;\n  }\n  html [dir=rtl] .md-search__inner {\n    right: 100%;\n    left: initial;\n    transform: translateX(-5%);\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__inner {\n    position: relative;\n    float: right;\n    width: 11.7rem;\n    padding: 0.1rem 0;\n    transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n  }\n  [dir=rtl] .md-search__inner {\n    float: left;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 34.4rem;\n  }\n}\n.md-search__form {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search__form {\n    border-radius: 0.1rem;\n  }\n}\n.md-search__input {\n  position: relative;\n  z-index: 2;\n  padding: 0 2.2rem 0 3.6rem;\n  text-overflow: ellipsis;\n  background-color: var(--md-default-bg-color);\n  box-shadow: 0 0 0.6rem transparent;\n  transition: color 250ms, background-color 250ms, box-shadow 250ms;\n}\n[dir=rtl] .md-search__input {\n  padding: 0 3.6rem 0 2.2rem;\n}\n.md-search__input::placeholder {\n  transition: color 250ms;\n}\n.md-search__input ~ .md-search__icon, .md-search__input::placeholder {\n  color: var(--md-default-fg-color--light);\n}\n.md-search__input::-ms-clear {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input {\n  box-shadow: 0 0 0.6rem rgba(0, 0, 0, 0.07);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__input {\n    width: 100%;\n    height: 2.4rem;\n    font-size: 0.9rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__input {\n    width: 100%;\n    height: 1.8rem;\n    padding-left: 2.2rem;\n    color: inherit;\n    font-size: 0.8rem;\n    background-color: rgba(0, 0, 0, 0.26);\n    border-radius: 0.1rem;\n  }\n  [dir=rtl] .md-search__input {\n    padding-right: 2.2rem;\n  }\n  .md-search__input + .md-search__icon {\n    color: var(--md-primary-bg-color);\n  }\n  .md-search__input::placeholder {\n    color: var(--md-primary-bg-color--light);\n  }\n  .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input {\n    color: var(--md-default-fg-color);\n    text-overflow: clip;\n    background-color: var(--md-default-bg-color);\n    border-radius: 0.1rem 0.1rem 0 0;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle=search]:checked ~ .md-header .md-search__input::placeholder {\n    color: var(--md-default-fg-color--light);\n  }\n}\n.md-search__icon {\n  position: absolute;\n  z-index: 2;\n  width: 1.2rem;\n  height: 1.2rem;\n  cursor: pointer;\n  transition: color 250ms, opacity 250ms;\n}\n.md-search__icon:hover {\n  opacity: 0.7;\n}\n.md-search__icon[for=__search] {\n  top: 0.3rem;\n  left: 0.5rem;\n}\n[dir=rtl] .md-search__icon[for=__search] {\n  right: 0.5rem;\n  left: initial;\n}\n[dir=rtl] .md-search__icon[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[for=__search] {\n    top: 0.6rem;\n    left: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[for=__search] {\n    right: 0.8rem;\n    left: initial;\n  }\n  .md-search__icon[for=__search] svg:first-child {\n    display: none;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__icon[for=__search] {\n    pointer-events: none;\n  }\n  .md-search__icon[for=__search] svg:last-child {\n    display: none;\n  }\n}\n.md-search__icon[type=reset] {\n  top: 0.3rem;\n  right: 0.5rem;\n  transform: scale(0.75);\n  opacity: 0;\n  transition: transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-search__icon[type=reset] {\n  right: initial;\n  left: 0.5rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[type=reset] {\n    top: 0.6rem;\n    right: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[type=reset] {\n    right: initial;\n    left: 0.8rem;\n  }\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset] {\n  transform: scale(1);\n  opacity: 1;\n  pointer-events: initial;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset]:hover {\n  opacity: 0.7;\n}\n.md-search__output {\n  position: absolute;\n  z-index: 1;\n  width: 100%;\n  overflow: hidden;\n  border-radius: 0 0 0.1rem 0.1rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__output {\n    top: 2.4rem;\n    bottom: 0;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__output {\n    top: 1.9rem;\n    opacity: 0;\n    transition: opacity 400ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__output {\n    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.4);\n    opacity: 1;\n  }\n}\n.md-search__scrollwrap {\n  height: 100%;\n  overflow-y: auto;\n  background-color: var(--md-default-bg-color);\n  backface-visibility: hidden;\n  touch-action: pan-y;\n}\n@media (max-resolution: 1dppx) {\n  .md-search__scrollwrap {\n    transform: translateZ(0);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search__scrollwrap {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-search__scrollwrap {\n    width: 34.4rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__scrollwrap {\n    max-height: 0;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__scrollwrap {\n    max-height: 75vh;\n  }\n  .md-search__scrollwrap:hover {\n    scrollbar-color: var(--md-accent-fg-color) transparent;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar {\n    width: 0.2rem;\n    height: 0.2rem;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb {\n    background-color: var(--md-default-fg-color--lighter);\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb:hover {\n    background-color: var(--md-accent-fg-color);\n  }\n}\n\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n}\n.md-search-result__meta {\n  padding: 0 0.8rem;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.8rem;\n  background-color: var(--md-default-fg-color--lightest);\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__meta {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__meta {\n    padding-right: 2.2rem;\n    padding-left: initial;\n  }\n}\n.md-search-result__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-search-result__item {\n  box-shadow: 0 -0.05rem 0 var(--md-default-fg-color--lightest);\n}\n.md-search-result__item:first-child {\n  box-shadow: none;\n}\n.md-search-result__link {\n  display: block;\n  outline: none;\n  transition: background-color 250ms;\n  scroll-snap-align: start;\n}\n.md-search-result__link:focus, .md-search-result__link:hover {\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__link:last-child p:last-child {\n  margin-bottom: 0.6rem;\n}\n.md-search-result__more summary {\n  display: block;\n  padding: 0.75em 0.8rem;\n  color: var(--md-typeset-a-color);\n  font-size: 0.64rem;\n  outline: 0;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__more summary {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__more summary {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__more summary:focus, .md-search-result__more summary:hover {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__more summary::marker, .md-search-result__more summary::-webkit-details-marker {\n  display: none;\n}\n.md-search-result__more summary ~ * > * {\n  opacity: 0.65;\n}\n.md-search-result__article {\n  position: relative;\n  padding: 0 0.8rem;\n  overflow: hidden;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__article {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__article {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__article--document .md-search-result__title {\n  margin: 0.55rem 0;\n  font-weight: 400;\n  font-size: 0.8rem;\n  line-height: 1.4;\n}\n.md-search-result__icon {\n  position: absolute;\n  left: 0;\n  width: 1.2rem;\n  height: 1.2rem;\n  margin: 0.5rem;\n  color: var(--md-default-fg-color--light);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search-result__icon {\n    display: none;\n  }\n}\n.md-search-result__icon::after {\n  display: inline-block;\n  width: 100%;\n  height: 100%;\n  background-color: currentColor;\n  mask-image: var(--md-search-result-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-search-result__icon {\n  right: 0;\n  left: initial;\n}\n[dir=rtl] .md-search-result__icon::after {\n  transform: scaleX(-1);\n}\n.md-search-result__title {\n  margin: 0.5em 0;\n  font-weight: 700;\n  font-size: 0.64rem;\n  line-height: 1.6;\n}\n.md-search-result__teaser {\n  display: -webkit-box;\n  max-height: 2rem;\n  margin: 0.5em 0;\n  overflow: hidden;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.6;\n  text-overflow: ellipsis;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n.md-search-result__teaser mark {\n  text-decoration: underline;\n  background-color: transparent;\n}\n.md-search-result__terms {\n  margin: 0.5em 0;\n  font-size: 0.64rem;\n  font-style: italic;\n}\n.md-search-result mark {\n  color: var(--md-accent-fg-color);\n  background-color: transparent;\n}\n\n.md-select {\n  position: relative;\n  z-index: 1;\n}\n.md-select__inner {\n  position: absolute;\n  top: calc(100% - 0.2rem);\n  left: 50%;\n  max-height: 0;\n  margin-top: 0.2rem;\n  color: var(--md-default-fg-color);\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  transform: translate3d(-50%, 0.3rem, 0);\n  opacity: 0;\n  transition: transform 250ms 375ms, opacity 250ms 250ms, max-height 0ms 500ms;\n}\n.md-select:focus-within .md-select__inner, .md-select:hover .md-select__inner {\n  max-height: 10rem;\n  transform: translate3d(-50%, 0, 0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms, max-height 250ms;\n}\n.md-select__inner::after {\n  position: absolute;\n  top: 0;\n  left: 50%;\n  width: 0;\n  height: 0;\n  margin-top: -0.2rem;\n  margin-left: -0.2rem;\n  border: 0.2rem solid transparent;\n  border-top: 0;\n  border-bottom-color: var(--md-default-bg-color);\n  content: \"\";\n}\n.md-select__list {\n  max-height: inherit;\n  margin: 0;\n  padding: 0;\n  overflow: auto;\n  font-size: 0.8rem;\n  list-style-type: none;\n  border-radius: 0.1rem;\n}\n.md-select__item {\n  line-height: 1.8rem;\n}\n.md-select__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  cursor: pointer;\n  transition: background-color 250ms, color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-select__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-select__link:focus, .md-select__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n.md-sidebar {\n  position: sticky;\n  top: 2.4rem;\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: 12.1rem;\n  padding: 1.2rem 0;\n}\n@media print {\n  .md-sidebar {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-sidebar--primary {\n    position: fixed;\n    top: 0;\n    left: -12.1rem;\n    z-index: 3;\n    display: block;\n    width: 12.1rem;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n    transform: translateX(0);\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms;\n  }\n  [dir=rtl] .md-sidebar--primary {\n    right: -12.1rem;\n    left: initial;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);\n    transform: translateX(12.1rem);\n  }\n  [dir=rtl] [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    transform: translateX(-12.1rem);\n  }\n  .md-sidebar--primary .md-sidebar__scrollwrap {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    margin: 0;\n    scroll-snap-type: none;\n    overflow: hidden;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar {\n    height: 0;\n  }\n  .no-js .md-sidebar {\n    height: auto;\n  }\n}\n.md-sidebar--secondary {\n  display: none;\n  order: 2;\n}\n@media screen and (min-width: 60em) {\n  .md-sidebar--secondary {\n    height: 0;\n  }\n  .no-js .md-sidebar--secondary {\n    height: auto;\n  }\n  .md-sidebar--secondary:not([hidden]) {\n    display: block;\n  }\n  .md-sidebar--secondary .md-sidebar__scrollwrap {\n    touch-action: pan-y;\n  }\n}\n.md-sidebar__scrollwrap {\n  margin: 0 0.2rem;\n  overflow-y: auto;\n  backface-visibility: hidden;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-sidebar__scrollwrap:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n\n@media screen and (max-width: 76.1875em) {\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    opacity: 0;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-overlay {\n    width: 100%;\n    height: 100%;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n  100% {\n    height: 0.65rem;\n  }\n}\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n  50% {\n    opacity: 0;\n  }\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n:root {\n  --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n  --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n  --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n  --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n.md-source {\n  display: block;\n  font-size: 0.65rem;\n  line-height: 1.2;\n  white-space: nowrap;\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n}\n.md-source:hover {\n  opacity: 0.7;\n}\n.md-source__icon {\n  display: inline-block;\n  width: 2rem;\n  height: 2.4rem;\n  vertical-align: middle;\n}\n.md-source__icon svg {\n  margin-top: 0.6rem;\n  margin-left: 0.6rem;\n}\n[dir=rtl] .md-source__icon svg {\n  margin-right: 0.6rem;\n  margin-left: initial;\n}\n.md-source__icon + .md-source__repository {\n  margin-left: -2rem;\n  padding-left: 2rem;\n}\n[dir=rtl] .md-source__icon + .md-source__repository {\n  margin-right: -2rem;\n  margin-left: initial;\n  padding-right: 2rem;\n  padding-left: initial;\n}\n.md-source__repository {\n  display: inline-block;\n  max-width: calc(100% - 1.2rem);\n  margin-left: 0.6rem;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  vertical-align: middle;\n}\n.md-source__facts {\n  margin: 0.1rem 0 0;\n  padding: 0;\n  overflow: hidden;\n  font-size: 0.55rem;\n  list-style-type: none;\n  opacity: 0.75;\n}\n[data-md-state=done] .md-source__facts {\n  animation: md-source__facts--done 250ms ease-in;\n}\n.md-source__fact {\n  display: inline-block;\n}\n[data-md-state=done] .md-source__fact {\n  animation: md-source__fact--done 400ms ease-out;\n}\n.md-source__fact::before {\n  display: inline-block;\n  width: 0.6rem;\n  height: 0.6rem;\n  margin-right: 0.1rem;\n  vertical-align: text-top;\n  background-color: currentColor;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-source__fact:nth-child(1n+2)::before {\n  margin-left: 0.4rem;\n}\n[dir=rtl] .md-source__fact {\n  margin-right: initial;\n  margin-left: 0.1rem;\n}\n[dir=rtl] .md-source__fact:nth-child(1n+2)::before {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-source__fact--version::before {\n  mask-image: var(--md-source-version-icon);\n}\n.md-source__fact--stars::before {\n  mask-image: var(--md-source-stars-icon);\n}\n.md-source__fact--forks::before {\n  mask-image: var(--md-source-forks-icon);\n}\n.md-source__fact--repositories::before {\n  mask-image: var(--md-source-repositories-icon);\n}\n\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n}\n@media print {\n  .md-tabs {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-tabs {\n    display: none;\n  }\n}\n.md-tabs[data-md-state=hidden] {\n  pointer-events: none;\n}\n.md-tabs__list {\n  margin: 0;\n  margin-left: 0.2rem;\n  padding: 0;\n  white-space: nowrap;\n  list-style: none;\n  contain: content;\n}\n[dir=rtl] .md-tabs__list {\n  margin-right: 0.2rem;\n  margin-left: initial;\n}\n.md-tabs__item {\n  display: inline-block;\n  height: 2.4rem;\n  padding-right: 0.6rem;\n  padding-left: 0.6rem;\n}\n.md-tabs__link {\n  display: block;\n  margin-top: 0.8rem;\n  font-size: 0.7rem;\n  backface-visibility: hidden;\n  opacity: 0.7;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms;\n}\n.md-tabs__link--active, .md-tabs__link:focus, .md-tabs__link:hover {\n  color: inherit;\n  opacity: 1;\n}\n.md-tabs__item:nth-child(2) .md-tabs__link {\n  transition-delay: 20ms;\n}\n.md-tabs__item:nth-child(3) .md-tabs__link {\n  transition-delay: 40ms;\n}\n.md-tabs__item:nth-child(4) .md-tabs__link {\n  transition-delay: 60ms;\n}\n.md-tabs__item:nth-child(5) .md-tabs__link {\n  transition-delay: 80ms;\n}\n.md-tabs__item:nth-child(6) .md-tabs__link {\n  transition-delay: 100ms;\n}\n.md-tabs__item:nth-child(7) .md-tabs__link {\n  transition-delay: 120ms;\n}\n.md-tabs__item:nth-child(8) .md-tabs__link {\n  transition-delay: 140ms;\n}\n.md-tabs__item:nth-child(9) .md-tabs__link {\n  transition-delay: 160ms;\n}\n.md-tabs__item:nth-child(10) .md-tabs__link {\n  transition-delay: 180ms;\n}\n.md-tabs__item:nth-child(11) .md-tabs__link {\n  transition-delay: 200ms;\n}\n.md-tabs__item:nth-child(12) .md-tabs__link {\n  transition-delay: 220ms;\n}\n.md-tabs__item:nth-child(13) .md-tabs__link {\n  transition-delay: 240ms;\n}\n.md-tabs__item:nth-child(14) .md-tabs__link {\n  transition-delay: 260ms;\n}\n.md-tabs__item:nth-child(15) .md-tabs__link {\n  transition-delay: 280ms;\n}\n.md-tabs__item:nth-child(16) .md-tabs__link {\n  transition-delay: 300ms;\n}\n.md-tabs[data-md-state=hidden] .md-tabs__link {\n  transform: translateY(50%);\n  opacity: 0;\n  transition: transform 0ms 100ms, opacity 100ms;\n}\n\n.md-top {\n  position: sticky;\n  bottom: 0.4rem;\n  z-index: 1;\n  float: right;\n  margin: -2.8rem 0.4rem 0.4rem;\n  padding: 0.4rem;\n  color: var(--md-primary-bg-color);\n  background: var(--md-primary-fg-color);\n  border-radius: 100%;\n  outline: none;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transform: translateY(0);\n  transition: opacity 125ms, transform 125ms cubic-bezier(0.4, 0, 0.2, 1), background-color 125ms;\n}\n[dir=rtl] .md-top {\n  float: left;\n}\n.md-top[data-md-state=hidden] {\n  transform: translateY(-0.2rem);\n  opacity: 0;\n}\n.md-top:focus, .md-top:hover {\n  background: var(--md-accent-fg-color);\n  transform: scale(1.1);\n}\n\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n.md-version {\n  flex-shrink: 0;\n  height: 2.4rem;\n  font-size: 0.8rem;\n}\n.md-version__current {\n  position: relative;\n  top: 0.05rem;\n  margin-right: 0.4rem;\n  margin-left: 1.4rem;\n}\n[dir=rtl] .md-version__current {\n  margin-right: 1.4rem;\n  margin-left: 0.4rem;\n}\n.md-version__current::after {\n  display: inline-block;\n  width: 0.4rem;\n  height: 0.6rem;\n  margin-left: 0.4rem;\n  background-color: currentColor;\n  mask-image: var(--md-version-icon);\n  mask-repeat: no-repeat;\n  content: \"\";\n}\n[dir=rtl] .md-version__current::after {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-version__list {\n  position: absolute;\n  top: 0.15rem;\n  z-index: 1;\n  max-height: 1.8rem;\n  margin: 0.2rem 0.8rem;\n  padding: 0;\n  overflow: auto;\n  color: var(--md-default-fg-color);\n  list-style-type: none;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  opacity: 0;\n  transition: max-height 0ms 500ms, opacity 250ms 250ms;\n  scroll-snap-type: y mandatory;\n}\n.md-version__list:focus-within, .md-version__list:hover {\n  max-height: 10rem;\n  opacity: 1;\n  transition: max-height 250ms, opacity 250ms;\n}\n.md-version__item {\n  line-height: 1.8rem;\n}\n.md-version__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  white-space: nowrap;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-version__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-version__link:focus, .md-version__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n:root {\n  --md-admonition-icon--note:\n    svg-load(\"material/pencil.svg\");\n  --md-admonition-icon--abstract:\n    svg-load(\"material/text-subject.svg\");\n  --md-admonition-icon--info:\n    svg-load(\"material/information.svg\");\n  --md-admonition-icon--tip:\n    svg-load(\"material/fire.svg\");\n  --md-admonition-icon--success:\n    svg-load(\"material/check-circle.svg\");\n  --md-admonition-icon--question:\n    svg-load(\"material/help-circle.svg\");\n  --md-admonition-icon--warning:\n    svg-load(\"material/alert.svg\");\n  --md-admonition-icon--failure:\n    svg-load(\"material/close-circle.svg\");\n  --md-admonition-icon--danger:\n    svg-load(\"material/flash-circle.svg\");\n  --md-admonition-icon--bug:\n    svg-load(\"material/bug.svg\");\n  --md-admonition-icon--example:\n    svg-load(\"material/format-list-numbered.svg\");\n  --md-admonition-icon--quote:\n    svg-load(\"material/format-quote-close.svg\");\n}\n\n.md-typeset .admonition, .md-typeset details {\n  margin: 1.5625em 0;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  color: var(--md-admonition-fg-color);\n  font-size: 0.64rem;\n  page-break-inside: avoid;\n  background-color: var(--md-admonition-bg-color);\n  border-left: 0.2rem solid #448aff;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.05);\n}\n@media print {\n  .md-typeset .admonition, .md-typeset details {\n    box-shadow: none;\n  }\n}\n[dir=rtl] .md-typeset .admonition, [dir=rtl] .md-typeset details {\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\n.md-typeset .admonition .admonition, .md-typeset details .admonition, .md-typeset .admonition details, .md-typeset details details {\n  margin-top: 1em;\n  margin-bottom: 1em;\n}\n.md-typeset .admonition .md-typeset__scrollwrap, .md-typeset details .md-typeset__scrollwrap {\n  margin: 1em -0.6rem;\n}\n.md-typeset .admonition .md-typeset__table, .md-typeset details .md-typeset__table {\n  padding: 0 0.6rem;\n}\n.md-typeset .admonition > .tabbed-set:only-child, .md-typeset details > .tabbed-set:only-child {\n  margin-top: 0;\n}\nhtml .md-typeset .admonition > :last-child, html .md-typeset details > :last-child {\n  margin-bottom: 0.6rem;\n}\n.md-typeset .admonition-title, .md-typeset summary {\n  position: relative;\n  margin: 0 -0.6rem 0 -0.8rem;\n  padding: 0.4rem 0.6rem 0.4rem 2rem;\n  font-weight: 700;\n  background-color: rgba(68, 138, 255, 0.1);\n  border-left: 0.2rem solid #448aff;\n}\n[dir=rtl] .md-typeset .admonition-title, [dir=rtl] .md-typeset summary {\n  margin: 0 -0.8rem 0 -0.6rem;\n  padding: 0.4rem 2rem 0.4rem 0.6rem;\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\nhtml .md-typeset .admonition-title:last-child, html .md-typeset summary:last-child {\n  margin-bottom: 0;\n}\n.md-typeset .admonition-title::before, .md-typeset summary::before {\n  position: absolute;\n  left: 0.6rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .admonition-title::before, [dir=rtl] .md-typeset summary::before {\n  right: 0.6rem;\n  left: initial;\n}\n.md-typeset .admonition-title + .tabbed-set:last-child, .md-typeset summary + .tabbed-set:last-child {\n  margin-top: 0;\n}\n\n.md-typeset .admonition.note, .md-typeset details.note {\n  border-color: #448aff;\n}\n\n.md-typeset .note > .admonition-title, .md-typeset .note > summary {\n  background-color: rgba(68, 138, 255, 0.1);\n  border-color: #448aff;\n}\n.md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before {\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.abstract, .md-typeset details.abstract, .md-typeset .admonition.tldr, .md-typeset details.tldr, .md-typeset .admonition.summary, .md-typeset details.summary {\n  border-color: #00b0ff;\n}\n\n.md-typeset .abstract > .admonition-title, .md-typeset .abstract > summary, .md-typeset .tldr > .admonition-title, .md-typeset .tldr > summary, .md-typeset .summary > .admonition-title, .md-typeset .summary > summary {\n  background-color: rgba(0, 176, 255, 0.1);\n  border-color: #00b0ff;\n}\n.md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before, .md-typeset .tldr > .admonition-title::before, .md-typeset .tldr > summary::before, .md-typeset .summary > .admonition-title::before, .md-typeset .summary > summary::before {\n  background-color: #00b0ff;\n  mask-image: var(--md-admonition-icon--abstract);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.info, .md-typeset details.info, .md-typeset .admonition.todo, .md-typeset details.todo {\n  border-color: #00b8d4;\n}\n\n.md-typeset .info > .admonition-title, .md-typeset .info > summary, .md-typeset .todo > .admonition-title, .md-typeset .todo > summary {\n  background-color: rgba(0, 184, 212, 0.1);\n  border-color: #00b8d4;\n}\n.md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before, .md-typeset .todo > .admonition-title::before, .md-typeset .todo > summary::before {\n  background-color: #00b8d4;\n  mask-image: var(--md-admonition-icon--info);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.tip, .md-typeset details.tip, .md-typeset .admonition.important, .md-typeset details.important, .md-typeset .admonition.hint, .md-typeset details.hint {\n  border-color: #00bfa5;\n}\n\n.md-typeset .tip > .admonition-title, .md-typeset .tip > summary, .md-typeset .important > .admonition-title, .md-typeset .important > summary, .md-typeset .hint > .admonition-title, .md-typeset .hint > summary {\n  background-color: rgba(0, 191, 165, 0.1);\n  border-color: #00bfa5;\n}\n.md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before, .md-typeset .important > .admonition-title::before, .md-typeset .important > summary::before, .md-typeset .hint > .admonition-title::before, .md-typeset .hint > summary::before {\n  background-color: #00bfa5;\n  mask-image: var(--md-admonition-icon--tip);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.success, .md-typeset details.success, .md-typeset .admonition.done, .md-typeset details.done, .md-typeset .admonition.check, .md-typeset details.check {\n  border-color: #00c853;\n}\n\n.md-typeset .success > .admonition-title, .md-typeset .success > summary, .md-typeset .done > .admonition-title, .md-typeset .done > summary, .md-typeset .check > .admonition-title, .md-typeset .check > summary {\n  background-color: rgba(0, 200, 83, 0.1);\n  border-color: #00c853;\n}\n.md-typeset .success > .admonition-title::before, .md-typeset .success > summary::before, .md-typeset .done > .admonition-title::before, .md-typeset .done > summary::before, .md-typeset .check > .admonition-title::before, .md-typeset .check > summary::before {\n  background-color: #00c853;\n  mask-image: var(--md-admonition-icon--success);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.question, .md-typeset details.question, .md-typeset .admonition.faq, .md-typeset details.faq, .md-typeset .admonition.help, .md-typeset details.help {\n  border-color: #64dd17;\n}\n\n.md-typeset .question > .admonition-title, .md-typeset .question > summary, .md-typeset .faq > .admonition-title, .md-typeset .faq > summary, .md-typeset .help > .admonition-title, .md-typeset .help > summary {\n  background-color: rgba(100, 221, 23, 0.1);\n  border-color: #64dd17;\n}\n.md-typeset .question > .admonition-title::before, .md-typeset .question > summary::before, .md-typeset .faq > .admonition-title::before, .md-typeset .faq > summary::before, .md-typeset .help > .admonition-title::before, .md-typeset .help > summary::before {\n  background-color: #64dd17;\n  mask-image: var(--md-admonition-icon--question);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.warning, .md-typeset details.warning, .md-typeset .admonition.attention, .md-typeset details.attention, .md-typeset .admonition.caution, .md-typeset details.caution {\n  border-color: #ff9100;\n}\n\n.md-typeset .warning > .admonition-title, .md-typeset .warning > summary, .md-typeset .attention > .admonition-title, .md-typeset .attention > summary, .md-typeset .caution > .admonition-title, .md-typeset .caution > summary {\n  background-color: rgba(255, 145, 0, 0.1);\n  border-color: #ff9100;\n}\n.md-typeset .warning > .admonition-title::before, .md-typeset .warning > summary::before, .md-typeset .attention > .admonition-title::before, .md-typeset .attention > summary::before, .md-typeset .caution > .admonition-title::before, .md-typeset .caution > summary::before {\n  background-color: #ff9100;\n  mask-image: var(--md-admonition-icon--warning);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.failure, .md-typeset details.failure, .md-typeset .admonition.missing, .md-typeset details.missing, .md-typeset .admonition.fail, .md-typeset details.fail {\n  border-color: #ff5252;\n}\n\n.md-typeset .failure > .admonition-title, .md-typeset .failure > summary, .md-typeset .missing > .admonition-title, .md-typeset .missing > summary, .md-typeset .fail > .admonition-title, .md-typeset .fail > summary {\n  background-color: rgba(255, 82, 82, 0.1);\n  border-color: #ff5252;\n}\n.md-typeset .failure > .admonition-title::before, .md-typeset .failure > summary::before, .md-typeset .missing > .admonition-title::before, .md-typeset .missing > summary::before, .md-typeset .fail > .admonition-title::before, .md-typeset .fail > summary::before {\n  background-color: #ff5252;\n  mask-image: var(--md-admonition-icon--failure);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.danger, .md-typeset details.danger, .md-typeset .admonition.error, .md-typeset details.error {\n  border-color: #ff1744;\n}\n\n.md-typeset .danger > .admonition-title, .md-typeset .danger > summary, .md-typeset .error > .admonition-title, .md-typeset .error > summary {\n  background-color: rgba(255, 23, 68, 0.1);\n  border-color: #ff1744;\n}\n.md-typeset .danger > .admonition-title::before, .md-typeset .danger > summary::before, .md-typeset .error > .admonition-title::before, .md-typeset .error > summary::before {\n  background-color: #ff1744;\n  mask-image: var(--md-admonition-icon--danger);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.bug, .md-typeset details.bug {\n  border-color: #f50057;\n}\n\n.md-typeset .bug > .admonition-title, .md-typeset .bug > summary {\n  background-color: rgba(245, 0, 87, 0.1);\n  border-color: #f50057;\n}\n.md-typeset .bug > .admonition-title::before, .md-typeset .bug > summary::before {\n  background-color: #f50057;\n  mask-image: var(--md-admonition-icon--bug);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.example, .md-typeset details.example {\n  border-color: #7c4dff;\n}\n\n.md-typeset .example > .admonition-title, .md-typeset .example > summary {\n  background-color: rgba(124, 77, 255, 0.1);\n  border-color: #7c4dff;\n}\n.md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before {\n  background-color: #7c4dff;\n  mask-image: var(--md-admonition-icon--example);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.quote, .md-typeset details.quote, .md-typeset .admonition.cite, .md-typeset details.cite {\n  border-color: #9e9e9e;\n}\n\n.md-typeset .quote > .admonition-title, .md-typeset .quote > summary, .md-typeset .cite > .admonition-title, .md-typeset .cite > summary {\n  background-color: rgba(158, 158, 158, 0.1);\n  border-color: #9e9e9e;\n}\n.md-typeset .quote > .admonition-title::before, .md-typeset .quote > summary::before, .md-typeset .cite > .admonition-title::before, .md-typeset .cite > summary::before {\n  background-color: #9e9e9e;\n  mask-image: var(--md-admonition-icon--quote);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n.md-typeset [id^=\"fnref:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n}\n.md-typeset [id^=\"fn:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n}\n.md-typeset .footnote {\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n}\n.md-typeset .footnote > ol {\n  margin-left: 0;\n}\n.md-typeset .footnote > ol > li {\n  transition: color 125ms;\n}\n.md-typeset .footnote > ol > li:target {\n  color: var(--md-default-fg-color);\n}\n.md-typeset .footnote > ol > li:hover .footnote-backref, .md-typeset .footnote > ol > li:target .footnote-backref {\n  transform: translateX(0);\n  opacity: 1;\n}\n.md-typeset .footnote > ol > li > :first-child {\n  margin-top: 0;\n}\n.md-typeset .footnote-backref {\n  display: inline-block;\n  color: var(--md-typeset-a-color);\n  font-size: 0;\n  vertical-align: text-bottom;\n  transform: translateX(0.25rem);\n  opacity: 0;\n  transition: color 250ms, transform 250ms 250ms, opacity 125ms 250ms;\n}\n@media print {\n  .md-typeset .footnote-backref {\n    color: var(--md-typeset-a-color);\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n[dir=rtl] .md-typeset .footnote-backref {\n  transform: translateX(-0.25rem);\n}\n.md-typeset .footnote-backref:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset .footnote-backref::before {\n  display: inline-block;\n  width: 0.8rem;\n  height: 0.8rem;\n  background-color: currentColor;\n  mask-image: var(--md-footnotes-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .footnote-backref::before svg {\n  transform: scaleX(-1);\n}\n\n.md-typeset .headerlink {\n  display: inline-block;\n  margin-left: 0.5rem;\n  color: var(--md-default-fg-color--lighter);\n  opacity: 0;\n  transition: color 250ms, opacity 125ms;\n}\n@media print {\n  .md-typeset .headerlink {\n    display: none;\n  }\n}\n[dir=rtl] .md-typeset .headerlink {\n  margin-right: 0.5rem;\n  margin-left: initial;\n}\n.md-typeset :hover > .headerlink,\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus {\n  opacity: 1;\n  transition: color 250ms, opacity 125ms;\n}\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus,\n.md-typeset .headerlink:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset :target {\n  scroll-margin-top: 3.6rem;\n}\n.md-typeset h1:target,\n.md-typeset h2:target,\n.md-typeset h3:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h1:target::before,\n.md-typeset h2:target::before,\n.md-typeset h3:target::before {\n  display: block;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n  content: \"\";\n}\n.md-typeset h4:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h4:target::before {\n  display: block;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n  content: \"\";\n}\n.md-typeset h5:target,\n.md-typeset h6:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h5:target::before,\n.md-typeset h6:target::before {\n  display: block;\n  margin-top: -3.6rem;\n  padding-top: 3.6rem;\n  content: \"\";\n}\n\n.md-typeset div.arithmatex {\n  overflow: auto;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset div.arithmatex {\n    margin: 0 -0.8rem;\n  }\n}\n.md-typeset div.arithmatex > * {\n  width: min-content;\n  margin: 1em auto !important;\n  padding: 0 0.8rem;\n  touch-action: auto;\n}\n\n.md-typeset del.critic,\n.md-typeset ins.critic,\n.md-typeset .critic.comment {\n  box-decoration-break: clone;\n}\n.md-typeset del.critic {\n  background-color: var(--md-typeset-del-color);\n}\n.md-typeset ins.critic {\n  background-color: var(--md-typeset-ins-color);\n}\n.md-typeset .critic.comment {\n  color: var(--md-code-hl-comment-color);\n}\n.md-typeset .critic.comment::before {\n  content: \"/* \";\n}\n.md-typeset .critic.comment::after {\n  content: \" */\";\n}\n.md-typeset .critic.block {\n  display: block;\n  margin: 1em 0;\n  padding-right: 0.8rem;\n  padding-left: 0.8rem;\n  overflow: auto;\n  box-shadow: none;\n}\n.md-typeset .critic.block > :first-child {\n  margin-top: 0.5em;\n}\n.md-typeset .critic.block > :last-child {\n  margin-bottom: 0.5em;\n}\n\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n.md-typeset details {\n  display: flow-root;\n  padding-top: 0;\n  overflow: visible;\n}\n.md-typeset details[open] > summary::after {\n  transform: rotate(90deg);\n}\n.md-typeset details:not([open]) {\n  padding-bottom: 0;\n  box-shadow: none;\n}\n.md-typeset details:not([open]) > summary {\n  border-radius: 0.1rem;\n}\n.md-typeset details::after {\n  display: table;\n  content: \"\";\n}\n.md-typeset summary {\n  display: block;\n  min-height: 1rem;\n  padding: 0.4rem 1.8rem 0.4rem 2rem;\n  border-top-left-radius: 0.1rem;\n  border-top-right-radius: 0.1rem;\n  cursor: pointer;\n}\n[dir=rtl] .md-typeset summary {\n  padding: 0.4rem 2.2rem 0.4rem 1.8rem;\n}\n.md-typeset summary:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset summary::after {\n  position: absolute;\n  top: 0.4rem;\n  right: 0.4rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: currentColor;\n  mask-image: var(--md-details-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  transform: rotate(0deg);\n  transition: transform 250ms;\n  content: \"\";\n}\n[dir=rtl] .md-typeset summary::after {\n  right: initial;\n  left: 0.4rem;\n  transform: rotate(180deg);\n}\n.md-typeset summary::marker, .md-typeset summary::-webkit-details-marker {\n  display: none;\n}\n\n.md-typeset .emojione,\n.md-typeset .twemoji,\n.md-typeset .gemoji {\n  display: inline-flex;\n  height: 1.125em;\n  vertical-align: text-top;\n}\n.md-typeset .emojione svg,\n.md-typeset .twemoji svg,\n.md-typeset .gemoji svg {\n  width: 1.125em;\n  max-height: 100%;\n  fill: currentColor;\n}\n\n.highlight .o,\n.highlight .ow {\n  color: var(--md-code-hl-operator-color);\n}\n.highlight .p {\n  color: var(--md-code-hl-punctuation-color);\n}\n.highlight .cpf,\n.highlight .l,\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .s2,\n.highlight .si,\n.highlight .s1,\n.highlight .ss {\n  color: var(--md-code-hl-string-color);\n}\n.highlight .cp,\n.highlight .se,\n.highlight .sh,\n.highlight .sr,\n.highlight .sx {\n  color: var(--md-code-hl-special-color);\n}\n.highlight .m,\n.highlight .mb,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo {\n  color: var(--md-code-hl-number-color);\n}\n.highlight .k,\n.highlight .kd,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .kc,\n.highlight .n {\n  color: var(--md-code-hl-name-color);\n}\n.highlight .no,\n.highlight .nb,\n.highlight .bp {\n  color: var(--md-code-hl-constant-color);\n}\n.highlight .nc,\n.highlight .ne,\n.highlight .nf,\n.highlight .nn {\n  color: var(--md-code-hl-function-color);\n}\n.highlight .nd,\n.highlight .ni,\n.highlight .nl,\n.highlight .nt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .c,\n.highlight .cm,\n.highlight .c1,\n.highlight .ch,\n.highlight .cs,\n.highlight .sd {\n  color: var(--md-code-hl-comment-color);\n}\n.highlight .na,\n.highlight .nv,\n.highlight .vc,\n.highlight .vg,\n.highlight .vi {\n  color: var(--md-code-hl-variable-color);\n}\n.highlight .ge,\n.highlight .gr,\n.highlight .gh,\n.highlight .go,\n.highlight .gp,\n.highlight .gs,\n.highlight .gu,\n.highlight .gt {\n  color: var(--md-code-hl-generic-color);\n}\n.highlight .gd,\n.highlight .gi {\n  margin: 0 -0.125em;\n  padding: 0 0.125em;\n  border-radius: 0.1rem;\n}\n.highlight .gd {\n  background-color: var(--md-typeset-del-color);\n}\n.highlight .gi {\n  background-color: var(--md-typeset-ins-color);\n}\n.highlight .hll {\n  display: block;\n  margin: 0 -1.1764705882em;\n  padding: 0 1.1764705882em;\n  background-color: var(--md-code-hl-color);\n}\n.highlight [data-linenos]::before {\n  position: sticky;\n  left: -1.1764705882em;\n  float: left;\n  margin-right: 1.1764705882em;\n  margin-left: -1.1764705882em;\n  padding-left: 1.1764705882em;\n  color: var(--md-default-fg-color--light);\n  background-color: var(--md-code-bg-color);\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n  content: attr(data-linenos);\n  user-select: none;\n}\n\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n}\n.highlighttable tbody,\n.highlighttable td {\n  display: block;\n  padding: 0;\n}\n.highlighttable tr {\n  display: flex;\n}\n.highlighttable pre {\n  margin: 0;\n}\n.highlighttable .linenos {\n  padding: 0.7720588235em 1.1764705882em;\n  padding-right: 0;\n  font-size: 0.85em;\n  background-color: var(--md-code-bg-color);\n  user-select: none;\n}\n.highlighttable .linenodiv {\n  padding-right: 0.5882352941em;\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n}\n.highlighttable .linenodiv pre {\n  color: var(--md-default-fg-color--light);\n  text-align: right;\n}\n.highlighttable .code {\n  flex: 1;\n  overflow: hidden;\n}\n\n.md-typeset .highlighttable {\n  margin: 1em 0;\n  direction: ltr;\n  border-radius: 0.1rem;\n}\n.md-typeset .highlighttable code {\n  border-radius: 0;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > .highlight {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > .highlight .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n  .md-typeset > .highlight code {\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable {\n    margin: 1em -0.8rem;\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n}\n\n.md-typeset .keys kbd::before,\n.md-typeset .keys kbd::after {\n  position: relative;\n  margin: 0;\n  color: inherit;\n  -moz-osx-font-smoothing: initial;\n  -webkit-font-smoothing: initial;\n}\n.md-typeset .keys span {\n  padding: 0 0.2em;\n  color: var(--md-default-fg-color--light);\n}\n.md-typeset .keys .key-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-left-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-right-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-left-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-right-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-left-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-right-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-left-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-right-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-left-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-right-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-left-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-right-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-left-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-right-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-left-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-right-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-arrow-down::before {\n  padding-right: 0.4em;\n  content: \"↓\";\n}\n.md-typeset .keys .key-arrow-left::before {\n  padding-right: 0.4em;\n  content: \"←\";\n}\n.md-typeset .keys .key-arrow-right::before {\n  padding-right: 0.4em;\n  content: \"→\";\n}\n.md-typeset .keys .key-arrow-up::before {\n  padding-right: 0.4em;\n  content: \"↑\";\n}\n.md-typeset .keys .key-backspace::before {\n  padding-right: 0.4em;\n  content: \"⌫\";\n}\n.md-typeset .keys .key-backtab::before {\n  padding-right: 0.4em;\n  content: \"⇤\";\n}\n.md-typeset .keys .key-caps-lock::before {\n  padding-right: 0.4em;\n  content: \"⇪\";\n}\n.md-typeset .keys .key-clear::before {\n  padding-right: 0.4em;\n  content: \"⌧\";\n}\n.md-typeset .keys .key-context-menu::before {\n  padding-right: 0.4em;\n  content: \"☰\";\n}\n.md-typeset .keys .key-delete::before {\n  padding-right: 0.4em;\n  content: \"⌦\";\n}\n.md-typeset .keys .key-eject::before {\n  padding-right: 0.4em;\n  content: \"⏏\";\n}\n.md-typeset .keys .key-end::before {\n  padding-right: 0.4em;\n  content: \"⤓\";\n}\n.md-typeset .keys .key-escape::before {\n  padding-right: 0.4em;\n  content: \"⎋\";\n}\n.md-typeset .keys .key-home::before {\n  padding-right: 0.4em;\n  content: \"⤒\";\n}\n.md-typeset .keys .key-insert::before {\n  padding-right: 0.4em;\n  content: \"⎀\";\n}\n.md-typeset .keys .key-page-down::before {\n  padding-right: 0.4em;\n  content: \"⇟\";\n}\n.md-typeset .keys .key-page-up::before {\n  padding-right: 0.4em;\n  content: \"⇞\";\n}\n.md-typeset .keys .key-print-screen::before {\n  padding-right: 0.4em;\n  content: \"⎙\";\n}\n.md-typeset .keys .key-tab::after {\n  padding-left: 0.4em;\n  content: \"⇥\";\n}\n.md-typeset .keys .key-num-enter::after {\n  padding-left: 0.4em;\n  content: \"⌤\";\n}\n.md-typeset .keys .key-enter::after {\n  padding-left: 0.4em;\n  content: \"⏎\";\n}\n\n.md-typeset .tabbed-content {\n  display: none;\n  order: 99;\n  width: 100%;\n  box-shadow: 0 -0.05rem var(--md-default-fg-color--lightest);\n}\n@media print {\n  .md-typeset .tabbed-content {\n    display: block;\n    order: initial;\n  }\n}\n.md-typeset .tabbed-content > pre:only-child,\n.md-typeset .tabbed-content > .highlight:only-child pre,\n.md-typeset .tabbed-content > .highlighttable:only-child {\n  margin: 0;\n}\n.md-typeset .tabbed-content > pre:only-child > code,\n.md-typeset .tabbed-content > .highlight:only-child pre > code,\n.md-typeset .tabbed-content > .highlighttable:only-child > code {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.md-typeset .tabbed-content > .tabbed-set {\n  margin: 0;\n}\n.md-typeset .tabbed-set {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 1em 0;\n  border-radius: 0.1rem;\n}\n.md-typeset .tabbed-set > input {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-typeset .tabbed-set > input:checked + label {\n  color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .tabbed-set > input:checked + label + .tabbed-content {\n  display: block;\n}\n.md-typeset .tabbed-set > input:focus + label {\n  outline-style: auto;\n}\n.md-typeset .tabbed-set > input:not(.focus-visible) + label {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset .tabbed-set > label {\n  z-index: 1;\n  width: auto;\n  padding: 0.9375em 1.25em 0.78125em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.64rem;\n  border-bottom: 0.1rem solid transparent;\n  cursor: pointer;\n  transition: color 250ms;\n}\n.md-typeset .tabbed-set > label:hover {\n  color: var(--md-accent-fg-color);\n}\n\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n.md-typeset .task-list-item {\n  position: relative;\n  list-style-type: none;\n}\n.md-typeset .task-list-item [type=checkbox] {\n  position: absolute;\n  top: 0.45em;\n  left: -2em;\n}\n[dir=rtl] .md-typeset .task-list-item [type=checkbox] {\n  right: -2em;\n  left: initial;\n}\n.md-typeset .task-list-control [type=checkbox] {\n  z-index: -1;\n  opacity: 0;\n}\n.md-typeset .task-list-indicator::before {\n  position: absolute;\n  top: 0.15em;\n  left: -1.5em;\n  width: 1.25em;\n  height: 1.25em;\n  background-color: var(--md-default-fg-color--lightest);\n  mask-image: var(--md-tasklist-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .task-list-indicator::before {\n  right: -1.5em;\n  left: initial;\n}\n.md-typeset [type=checkbox]:checked + .task-list-indicator::before {\n  background-color: #00e676;\n  mask-image: var(--md-tasklist-icon--checked);\n}\n\n@media screen and (min-width: 45em) {\n  .md-typeset .inline {\n    float: left;\n    width: 11.7rem;\n    margin-top: 0;\n    margin-right: 0.8rem;\n    margin-bottom: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  .md-typeset .inline.end {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline.end {\n    float: left;\n    margin-right: 0.8rem;\n    margin-left: 0;\n  }\n}\n\n/*# sourceMappingURL=main.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Enforce correct box model and prevent adjustments of font size after\n// orientation changes in IE and iOS\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n// All elements shall inherit the document default\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\n// Remove margin in all browsers\nbody {\n  margin: 0;\n}\n\n// Reset tap outlines on iOS and Android\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\n// Reset link styles\na {\n  color: inherit;\n  text-decoration: none;\n}\n\n// Normalize horizontal separator styles\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: px2rem(1px);\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\n// Normalize font-size in all browsers\nsmall {\n  font-size: 80%;\n}\n\n// Prevent subscript and superscript from affecting line-height\nsub,\nsup {\n  line-height: 1em;\n}\n\n// Remove border on image\nimg {\n  border-style: none;\n}\n\n// Reset table styles\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\n// Reset table cell styles\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\n// Reset button styles\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\n// Reset input styles\ninput {\n  border: 0;\n  outline: none;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Color definitions\n:root {\n\n  // Default color shades\n  --md-default-fg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter:      hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest:     hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter:      hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest:     hsla(0, 0%, 100%, 0.12);\n\n  // Primary color shades\n  --md-primary-fg-color:               hsla(#{hex2hsl($clr-indigo-500)}, 1);\n  --md-primary-fg-color--light:        hsla(#{hex2hsl($clr-indigo-400)}, 1);\n  --md-primary-fg-color--dark:         hsla(#{hex2hsl($clr-indigo-700)}, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Accent color shades\n  --md-accent-fg-color:                hsla(#{hex2hsl($clr-indigo-a200)}, 1);\n  --md-accent-fg-color--transparent:   hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);\n  --md-accent-bg-color:                hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light:         hsla(0, 0%, 100%, 0.7);\n\n  // Light theme (default)\n  > * {\n\n    // Code color shades\n    --md-code-fg-color:                hsla(200, 18%, 26%, 1);\n    --md-code-bg-color:                hsla(0, 0%, 96%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n    --md-code-hl-number-color:         hsla(0, 67%, 50%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 47%, 1);\n    --md-code-hl-function-color:       hsla(291, 45%, 50%, 1);\n    --md-code-hl-constant-color:       hsla(250, 63%, 60%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 54%, 51%, 1);\n    --md-code-hl-string-color:         hsla(150, 63%, 30%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n\n    // Typeset `del` and `ins` color shades\n    --md-typeset-del-color:            hsla(6, 90%, 60%, 0.15);\n    --md-typeset-ins-color:            hsla(150, 90%, 44%, 0.15);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(0, 0%, 98%, 1);\n    --md-typeset-kbd-accent-color:     hsla(0, 100%, 100%, 1);\n    --md-typeset-kbd-border-color:     hsla(0, 0%, 72%, 1);\n\n    // Admonition color shades\n    --md-admonition-fg-color:          var(--md-default-fg-color);\n    --md-admonition-bg-color:          var(--md-default-bg-color);\n\n    // Footer color shades\n    --md-footer-fg-color:              hsla(0, 0%, 100%, 1);\n    --md-footer-fg-color--light:       hsla(0, 0%, 100%, 0.7);\n    --md-footer-fg-color--lighter:     hsla(0, 0%, 100%, 0.3);\n    --md-footer-bg-color:              hsla(0, 0%, 0%, 0.87);\n    --md-footer-bg-color--dark:        hsla(0, 0%, 0%, 0.32);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon\n.md-icon {\n\n  // SVG defaults\n  svg {\n    display: block;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    fill: currentColor;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: font definitions\n// ----------------------------------------------------------------------------\n\n// Enable font-smoothing in Webkit and FF\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// Define default fonts\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family:\n    var(--md-text-font-family, _),\n    -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\n// Define monospaced fonts\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family:\n    var(--md-code-font-family, _),\n    SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: typesetted content\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Content that is typeset - if possible, all margins, paddings and font sizes\n// should be set in ems, so nested blocks (e.g. admonitions) render correctly.\n.md-typeset {\n  font-size: px2rem(16px);\n  line-height: 1.6;\n  color-adjust: exact;\n\n  // [print]: We'll use a smaller `font-size` for printing, so code examples\n  // don't break too early, and `16px` looks too big anyway.\n  @media print {\n    font-size: px2rem(13.6px);\n  }\n\n  // Default spacing\n  ul,\n  ol,\n  dl,\n  figure,\n  blockquote,\n  pre {\n    display: flow-root;\n    margin: 1em 0;\n  }\n\n  // Headline on level 1\n  h1 {\n    margin: 0 0 px2em(40px, 32px);\n    color: var(--md-default-fg-color--light);\n    font-weight: 300;\n    font-size: px2em(32px);\n    line-height: 1.3;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 2\n  h2 {\n    margin: px2em(40px, 25px) 0 px2em(16px, 25px);\n    font-weight: 300;\n    font-size: px2em(25px);\n    line-height: 1.4;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3\n  h3 {\n    margin: px2em(32px, 20px) 0 px2em(16px, 20px);\n    font-weight: 400;\n    font-size: px2em(20px);\n    line-height: 1.5;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3 following level 2\n  h2 + h3 {\n    margin-top: px2em(16px, 20px);\n  }\n\n  // Headline on level 4\n  h4 {\n    margin: px2em(16px) 0;\n    font-weight: 700;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5-6\n  h5,\n  h6 {\n    margin: px2em(16px, 12.8px) 0;\n    color: var(--md-default-fg-color--light);\n    font-weight: 700;\n    font-size: px2em(12.8px);\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5\n  h5 {\n    text-transform: uppercase;\n  }\n\n  // Horizontal separator\n  hr {\n    display: flow-root;\n    margin: 1.5em 0;\n    border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);\n  }\n\n  // Text link\n  a {\n    color: var(--md-typeset-a-color);\n    word-break: break-word;\n\n    // Also enable color transition on pseudo elements\n    &,\n    &::before {\n      transition: color 125ms;\n    }\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Code block\n  code,\n  pre,\n  kbd {\n    color: var(--md-code-fg-color);\n    direction: ltr;\n\n    // [print]: Wrap text and hide scollbars\n    @media print {\n      white-space: pre-wrap;\n    }\n  }\n\n  // Inline code block\n  code {\n    padding: 0 px2em(4px, 13.6px);\n    font-size: px2em(13.6px);\n    word-break: break-word;\n    background-color: var(--md-code-bg-color);\n    border-radius: px2rem(2px);\n    box-decoration-break: clone;\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n  }\n\n  // Code block in headline\n  h1 code,\n  h2 code,\n  h3 code,\n  h4 code,\n  h5 code,\n  h6 code {\n    margin: initial;\n    padding: initial;\n    background-color: transparent;\n    box-shadow: none;\n  }\n\n  // Ensure link color in code blocks\n  a code {\n    color: currentColor;\n  }\n\n  // Unformatted content\n  pre {\n    position: relative;\n    line-height: 1.4;\n\n    // Code block\n    > code {\n      display: block;\n      margin: 0;\n      padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n      overflow: auto;\n      word-break: normal;\n      box-shadow: none;\n      box-decoration-break: slice;\n      touch-action: auto;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Code block on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Unformatted text\n    > pre {\n      margin: 1em px2rem(-16px);\n\n      // Code block\n      code {\n        border-radius: 0;\n      }\n    }\n  }\n\n  // Keyboard key\n  kbd {\n    display: inline-block;\n    padding: 0 px2em(8px, 12px);\n    color: var(--md-default-fg-color);\n    font-size: px2em(12px);\n    vertical-align: text-top;\n    word-break: break-word;\n    background-color: var(--md-typeset-kbd-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(2px)  0 px2rem(1px) var(--md-typeset-kbd-border-color),\n      0 px2rem(2px)  0             var(--md-typeset-kbd-border-color),\n      0 px2rem(-2px) px2rem(4px)   var(--md-typeset-kbd-accent-color) inset;\n  }\n\n  // Text highlighting marker\n  mark {\n    color: inherit;\n    word-break: break-word;\n    background-color: var(--md-typeset-mark-color);\n    box-decoration-break: clone;\n  }\n\n  // Abbreviation\n  abbr {\n    text-decoration: none;\n    border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);\n    cursor: help;\n\n    // Show tooltip for touch devices\n    @media (hover: none) {\n      position: relative;\n\n      // Tooltip\n      &[title]:focus::after,\n      &[title]:hover::after {\n        @include z-depth(2);\n\n        position: absolute;\n        left: 0;\n        display: inline-block;\n        width: auto;\n        min-width: max-content;\n        max-width: 80%;\n        margin-top: 2em;\n        padding: px2rem(4px) px2rem(6px);\n        color: var(--md-default-bg-color);\n        font-size: px2rem(14px);\n        background-color: var(--md-default-fg-color);\n        border-radius: px2rem(2px);\n        content: attr(title);\n      }\n    }\n  }\n\n  // Small text\n  small {\n    opacity: 0.75;\n  }\n\n  // Superscript and subscript\n  sup,\n  sub {\n    margin-left: px2em(1px, 12.8px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(1px, 12.8px);\n      margin-left: initial;\n    }\n  }\n\n  // Blockquotes, possibly nested\n  blockquote {\n    padding-left: px2rem(12px);\n    color: var(--md-default-fg-color--light);\n    border-left: px2rem(4px) solid var(--md-default-fg-color--lighter);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: initial;\n      border-right: px2rem(4px) solid var(--md-default-fg-color--lighter);\n      border-left: initial;\n    }\n  }\n\n  // Unordered list\n  ul {\n    list-style-type: disc;\n  }\n\n  // Unordered and ordered list\n  ul,\n  ol {\n    margin-left: px2em(10px);\n    padding: 0;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(10px);\n      margin-left: initial;\n    }\n\n    // Nested ordered list\n    ol {\n      list-style-type: lower-alpha;\n\n      // Triply nested ordered list\n      ol {\n        list-style-type: lower-roman;\n      }\n    }\n\n    // List element\n    li {\n      margin-bottom: 0.5em;\n      margin-left: px2em(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2em(20px);\n        margin-left: initial;\n      }\n\n      // Adjust spacing\n      p,\n      blockquote {\n        margin: 0.5em 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Nested list\n      ul,\n      ol {\n        margin: 0.5em 0 0.5em px2em(10px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2em(10px);\n          margin-left: initial;\n        }\n      }\n    }\n  }\n\n  // Definition list\n  dd {\n    margin: 1em 0 1.5em px2em(30px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(30px);\n      margin-left: initial;\n    }\n  }\n\n  // Image or icon\n  img,\n  svg {\n    max-width: 100%;\n    height: auto;\n\n    // Adjust spacing when left-aligned\n    &[align=\"left\"] {\n      margin: 1em;\n      margin-left: 0;\n    }\n\n    // Adjust spacing when right-aligned\n    &[align=\"right\"] {\n      margin: 1em;\n      margin-right: 0;\n    }\n\n    // Adjust spacing when sole children\n    &[align]:only-child {\n      margin-top: 0;\n    }\n  }\n\n  // Figure\n  figure {\n    width: fit-content;\n    max-width: 100%;\n    margin: 0 auto;\n    text-align: center;\n\n    // Figure images\n    img {\n      display: block;\n    }\n  }\n\n  // Figure caption\n  figcaption {\n    max-width: px2rem(480px);\n    margin: 1em auto 2em;\n    font-style: italic;\n  }\n\n  // Limit width to container\n  iframe {\n    max-width: 100%;\n  }\n\n  // Data table\n  table:not([class]) {\n    display: inline-block;\n    max-width: 100%;\n    overflow: auto;\n    font-size: px2rem(12.8px);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    touch-action: auto;\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: table;\n    }\n\n    // Due to margin collapse because of the necessary inline-block hack, we\n    // cannot increase the bottom margin on the table, so we just increase the\n    // top margin on the following element\n    + * {\n      margin-top: 1.5em;\n    }\n\n    // Elements in table heading and cell\n    th > *,\n    td > * {\n\n      // Adjust spacing on first child\n      &:first-child {\n        margin-top: 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    // Table heading and cell\n    th:not([align]),\n    td:not([align]) {\n      text-align: left;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        text-align: right;\n      }\n    }\n\n    // Table heading\n    th {\n      min-width: px2rem(100px);\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      color: var(--md-default-bg-color);\n      vertical-align: top;\n      background-color: var(--md-default-fg-color--light);\n\n      // Links in table headings\n      a {\n        color: inherit;\n      }\n    }\n\n    // Table cell\n    td {\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      vertical-align: top;\n      border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n    }\n\n    // Table row\n    tr {\n      transition: background-color 125ms;\n\n      // Table row on hover\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.035);\n        box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;\n      }\n\n      // Hide border on first table row\n      &:first-child td {\n        border-top: 0;\n      }\n    }\n\n    // Text link in table\n    a {\n      word-break: normal;\n    }\n  }\n\n  // Sortable table\n  table th[role=\"columnheader\"] {\n    cursor: pointer;\n\n    // Sort icon\n    &::after {\n      display: inline-block;\n      width: 1.2em;\n      height: 1.2em;\n      margin-left: 0.5em;\n      vertical-align: sub;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Sort ascending\n    &[aria-sort=\"ascending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--ascending);\n    }\n\n    // Sort descending\n    &[aria-sort=\"descending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--descending);\n    }\n  }\n\n  // Data table scroll wrapper\n  &__scrollwrap {\n    margin: 1em px2rem(-16px);\n    overflow-x: auto;\n    touch-action: auto;\n  }\n\n  // Data table wrapper\n  &__table {\n    display: inline-block;\n    margin-bottom: 0.5em;\n    padding: 0 px2rem(16px);\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: block;\n    }\n\n    // Data table\n    html & table {\n      display: table;\n      width: 100%;\n      margin: 0;\n      overflow: hidden;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","//\n// Name:           Material Shadows\n// Description:    Mixins for Material Design Shadows.\n// Version:        3.0.1\n//\n// Author:         Denis Malinochkin\n// Git:            https://github.com/mrmlnc/material-shadows\n//\n// twitter:        @mrmlnc\n//\n// ------------------------------------\n\n\n// Mixins\n// ------------------------------------\n\n@mixin z-depth-transition() {\n  transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1);\n}\n\n@mixin z-depth-focus() {\n  box-shadow: 0 0 8px rgba(0, 0, 0, .18), 0 8px 16px rgba(0, 0, 0, .36);\n}\n\n@mixin z-depth-2dp() {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14),\n              0 1px 5px 0 rgba(0, 0, 0, .12),\n              0 3px 1px -2px rgba(0, 0, 0, .2);\n}\n\n@mixin z-depth-3dp() {\n  box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .14),\n              0 1px 8px 0 rgba(0, 0, 0, .12),\n              0 3px 3px -2px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-4dp() {\n  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14),\n              0 1px 10px 0 rgba(0, 0, 0, .12),\n              0 2px 4px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-6dp() {\n  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14),\n              0 1px 18px 0 rgba(0, 0, 0, .12),\n              0 3px 5px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-8dp() {\n  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14),\n              0 3px 14px 2px rgba(0, 0, 0, .12),\n              0 5px 5px -3px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-16dp() {\n  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14),\n              0  6px 30px 5px rgba(0, 0, 0, .12),\n              0  8px 10px -5px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-24dp() {\n  box-shadow: 0  9px 46px  8px rgba(0, 0, 0, .14),\n              0 24px 38px  3px rgba(0, 0, 0, .12),\n              0 11px 15px -7px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth($dp: 2) {\n  @if $dp == 2 {\n    @include z-depth-2dp();\n  } @else if $dp == 3 {\n    @include z-depth-3dp();\n  } @else if $dp == 4 {\n    @include z-depth-4dp();\n  } @else if $dp == 6 {\n    @include z-depth-6dp();\n  } @else if $dp == 8 {\n    @include z-depth-8dp();\n  } @else if $dp == 16 {\n    @include z-depth-16dp();\n  } @else if $dp == 24 {\n    @include z-depth-24dp();\n  }\n}\n\n\n// Class generator\n// ------------------------------------\n\n@mixin z-depth-classes($transition: false, $focus: false) {\n  @if $transition == true {\n    &-transition {\n      @include z-depth-transition();\n    }\n  }\n\n  @if $focus == true {\n    &-focus {\n      @include z-depth-focus();\n    }\n  }\n\n  // The available values for the shadow depth\n  @each $depth in 2, 3, 4, 6, 8, 16, 24 {\n    &-#{$depth}dp {\n      @include z-depth($depth);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: base grid and containers\n// ----------------------------------------------------------------------------\n\n// Stretch container to viewport and set base `font-size`\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  // Hack: normally, we would set the base `font-size` to `62.5%`, so we can\n  // base all calculations on `10px`, but Chromium and Chrome define a minimal\n  // `font-size` of `12px` if the system language is set to Chinese. For this\n  // reason we just double the `font-size` and set it to `20px`.\n  //\n  // See https://github.com/squidfunk/mkdocs-material/issues/911\n  font-size: 125%;\n\n  // [screen medium +]: Set base `font-size` to `11px`\n  @include break-from-device(screen medium) {\n    font-size: 137.5%;\n  }\n\n  // [screen large +]: Set base `font-size` to `12px`\n  @include break-from-device(screen large) {\n    font-size: 150%;\n  }\n}\n\n// Stretch body to container - flexbox is used, so the footer will always be\n// aligned to the bottom of the viewport\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  // Hack: reset `font-size` to `10px`, so the spacing for all inline elements\n  // is correct again. Otherwise the spacing would be based on `20px`.\n  font-size: px2rem(10px);\n  background-color: var(--md-default-bg-color);\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n\n  // Body in locked state\n  &[data-md-state=\"lock\"] {\n\n    // [tablet portrait -]: Omit scroll bubbling\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Grid container - this class is applied to wrapper elements within the\n// header, content area and footer, and makes sure that their width is limited\n// to `1220px`, and they are rendered centered if the screen is larger.\n.md-grid {\n  max-width: px2rem(1220px);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n// Main container\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n}\n\n// Main area - stretch to remaining space of container\n.md-main {\n  flex-grow: 1;\n\n  // Main area wrapper\n  &__inner {\n    display: flex;\n    height: 100%;\n    margin-top: px2rem(24px + 6px);\n  }\n}\n\n// Add ellipsis in case of overflowing text\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: navigational elements\n// ----------------------------------------------------------------------------\n\n// Toggle - this class is applied to checkbox elements, which are used to\n// implement the CSS-only drawer and navigation, as well as the search\n.md-toggle {\n  display: none;\n}\n\n// Option - this class is applied to radio elements, which are used to\n// implement the color palette toggle\n.md-option {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n\n  // Option label for checked radio button\n  &:checked + label:not([hidden]) {\n    display: block;\n  }\n\n  // Option label on focus\n  &.focus-visible + label {\n    outline-style: auto;\n  }\n}\n\n// Skip link\n.md-skip {\n  position: fixed;\n  // Hack: if we don't set the negative `z-index`, the skip link will force the\n  // creation of new layers when code blocks are near the header on scrolling\n  z-index: -1;\n  margin: px2rem(10px);\n  padding: px2rem(6px) px2rem(10px);\n  color: var(--md-default-bg-color);\n  font-size: px2rem(12.8px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(px2rem(8px));\n  opacity: 0;\n\n  // Show skip link on focus\n  &:focus {\n    z-index: 10;\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n      opacity   175ms 75ms;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: print styles\n// ----------------------------------------------------------------------------\n\n// Add margins to page\n@page {\n  margin: 25mm;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Announcement bar\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide announcement bar\n  @media print {\n    display: none;\n  }\n\n  // Announcement wrapper\n  &__inner {\n    margin: px2rem(12px) auto;\n    padding: 0 px2rem(16px);\n    color: var(--md-footer-fg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Button to copy to clipboard\n.md-clipboard {\n  position: absolute;\n  top: px2em(8px);\n  right: px2em(8px);\n  z-index: 1;\n  width: px2em(24px);\n  height: px2em(24px);\n  color: var(--md-default-fg-color--lightest);\n  border-radius: px2rem(2px);\n  cursor: pointer;\n  transition: color 250ms;\n\n  // [print]: Hide button\n  @media print {\n    display: none;\n  }\n\n  // Hide outline for pointer devices\n  &:not(.focus-visible) {\n    outline: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  // Darken color on code block hover\n  :hover > & {\n    color: var(--md-default-fg-color--light);\n  }\n\n  // Button on focus/hover\n  &:focus,\n  &:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Button icon - the width and height are defined in `em`, so the size is\n  // automatically adjusted for nested code blocks (e.g. in admonitions)\n  &::after {\n    display: block;\n    width: px2em(18px);\n    height: px2em(18px);\n    margin: 0 auto;\n    background-color: currentColor;\n    mask-image: var(--md-clipboard-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n\n  // Inline button\n  &--inline {\n    cursor: pointer;\n\n    // Code block\n    code {\n      transition:\n        color            250ms,\n        background-color 250ms;\n    }\n\n    // Code block on focus/hover\n    &:focus code,\n    &:hover code {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Content area\n.md-content {\n  flex-grow: 1;\n  // Hack: we must use `overflow: hidden`, so the content area is capped by\n  // the dimensions of its parent. Otherwise, long code blocks might lead to\n  // a wider content area which will break everything. This, however, induces\n  // margin collapse, which will break scroll margins. Adding a large enough\n  // scroll padding seems to do the trick, at least in Chrome and Firefox.\n  overflow: hidden;\n  scroll-padding-top: px2rem(1024px);\n\n  // Content wrapper\n  &__inner {\n    margin: 0 px2rem(16px) px2rem(24px);\n    padding-top: px2rem(12px);\n\n    // [screen +]: Adjust spacing between content area and sidebars\n    @include break-from-device(screen) {\n\n      // Sidebar with navigation is visible\n      .md-sidebar--primary:not([hidden]) ~ .md-content > & {\n        margin-left: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(24px);\n          margin-left: px2rem(16px);\n        }\n      }\n\n      // Sidebar with table of contents is visible\n      .md-sidebar--secondary:not([hidden]) ~ .md-content > & {\n        margin-right: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(16px);\n          margin-left: px2rem(24px);\n        }\n      }\n    }\n\n    // Hack: add pseudo element for spacing, as the overflow of the content\n    // container may not be hidden due to an imminent offset error on targets\n    &::before {\n      display: block;\n      height: px2rem(8px);\n      content: \"\";\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  // Button inside of the content area - these buttons are meant for actions on\n  // a document-level, i.e. linking to related source code files, printing etc.\n  &__button {\n    float: right;\n    margin: px2rem(8px) 0;\n    margin-left: px2rem(8px);\n    padding: 0;\n\n    // [print]: Hide buttons\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: left;\n      margin-right: px2rem(8px);\n      margin-left: initial;\n\n      // Flip icon vertically\n      svg {\n        transform: scaleX(-1);\n      }\n    }\n\n    // Adjust default link color for icons\n    .md-typeset & {\n      color: var(--md-default-fg-color--lighter);\n    }\n\n    // Align with body copy located next to icon\n    svg {\n      display: inline;\n      vertical-align: top;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Dialog\n.md-dialog {\n  @include z-depth(2);\n\n  position: fixed;\n  right: px2rem(16px);\n  bottom: px2rem(16px);\n  left: initial;\n  z-index: 2;\n  min-width: px2rem(222px);\n  padding: px2rem(8px) px2rem(12px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(100%);\n  opacity: 0;\n  transition:\n    transform 0ms   400ms,\n    opacity   400ms;\n  pointer-events: none;\n\n  // [print]: Hide dialog\n  @media print {\n    display: none;\n  }\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    right: initial;\n    left: px2rem(16px);\n  }\n\n  // Dialog in open state\n  &[data-md-state=\"open\"] {\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),\n      opacity   400ms;\n    pointer-events: initial;\n  }\n\n  // Dialog wrapper\n  &__inner {\n    color: var(--md-default-bg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Form button\n  .md-button {\n    display: inline-block;\n    padding: px2em(10px) px2em(32px);\n    color: var(--md-primary-fg-color);\n    font-weight: 700;\n    border: px2rem(2px) solid currentColor;\n    border-radius: px2rem(2px);\n    transition:\n      color            125ms,\n      background-color 125ms,\n      border-color     125ms;\n\n    // Primary button\n    &--primary {\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color);\n      border-color: var(--md-primary-fg-color);\n    }\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-bg-color);\n      background-color: var(--md-accent-fg-color);\n      border-color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Form input\n  .md-input {\n    height: px2rem(36px);\n    padding: 0 px2rem(12px);\n    font-size: px2rem(16px);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    transition: box-shadow 250ms;\n\n    // Input on focus/hover\n    &:focus,\n    &:hover {\n      box-shadow:\n        0 px2rem(8px)   px2rem(20px) hsla(0, 0%, 0%, 0.15),\n        0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.15);\n    }\n\n    // Stretch to full width\n    &--stretch {\n      width: 100%;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Header - by default, the header will be sticky and stay always on top of the\n// viewport. If this behavior is not desired, just set `position: static`.\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  // Hack: reduce jitter by adding a transparent box shadow of the same size\n  // so the size of the layer doesn't change during animation\n  box-shadow:\n    0 0           px2rem(4px) rgba(0, 0, 0, 0),\n    0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);\n\n  // [print]: Hide header\n  @media print {\n    display: none;\n  }\n\n  // Header in shadow state, i.e. shadow is visible\n  &[data-md-state=\"shadow\"] {\n    box-shadow:\n      0 0           px2rem(4px) rgba(0, 0, 0, 0.1),\n      0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);\n    transition:\n      transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      box-shadow 250ms;\n  }\n\n  // Header in hidden state, i.e. moved out of sight\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(-100%);\n    transition:\n      transform  250ms cubic-bezier(0.8, 0, 0.6, 1),\n      box-shadow 250ms;\n  }\n\n  // Link or button on focus\n  .focus-visible {\n    outline-color: currentColor;\n  }\n\n  // Header wrapper\n  &__inner {\n    display: flex;\n    align-items: center;\n    padding: 0 px2rem(4px);\n  }\n\n  // Header button\n  &__button {\n    position: relative;\n    z-index: 1;\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n    color: currentColor;\n    vertical-align: middle;\n    cursor: pointer;\n    transition: opacity 250ms;\n\n    // Button on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Header button is visible\n    &:not([hidden]) {\n      display: inline-block;\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Button with logo, pointing to `config.site_url`\n    &.md-logo {\n      margin: px2rem(4px);\n      padding: px2rem(8px);\n\n      // [tablet -]: Hide button\n      @include break-to-device(tablet) {\n        display: none;\n      }\n\n      // Image or icon\n      img,\n      svg {\n        display: block;\n        width: px2rem(24px);\n        height: px2rem(24px);\n        fill: currentColor;\n      }\n    }\n\n    // Button for search\n    &[for=\"__search\"] {\n\n      // [tablet landscape +]: Hide button\n      @include break-from-device(tablet landscape) {\n        display: none;\n      }\n\n      // [no-js]: Hide button\n      .no-js & {\n        display: none;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Button for drawer\n    &[for=\"__drawer\"] {\n\n      // [screen +]: Hide button\n      @include break-from-device(screen) {\n        display: none;\n      }\n    }\n  }\n\n  // Header topic\n  &__topic {\n    position: absolute;\n    display: flex;\n    max-width: 100%;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   150ms;\n\n    // Second header topic - title of the current page\n    & + & {\n      z-index: -1;\n      transform: translateX(px2rem(25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(-25px));\n      }\n    }\n  }\n\n  // Header title\n  &__title {\n    flex-grow: 1;\n    height: px2rem(48px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n\n    // Header title in active state, i.e. page title is visible\n    &[data-md-state=\"active\"] .md-header__topic {\n      z-index: -1;\n      transform: translateX(px2rem(-25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(25px));\n      }\n\n      // Second header topic - title of the current page\n      + .md-header__topic {\n        z-index: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms;\n        pointer-events: initial;\n      }\n    }\n\n    // Add ellipsis in case of overflowing text\n    > .md-header__ellipsis {\n      position: relative;\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  // Header option\n  &__option {\n    display: flex;\n    flex-shrink: 0;\n    max-width: 100%;\n    white-space: nowrap;\n    transition:\n      max-width  0ms 250ms,\n      opacity  250ms 250ms;\n\n    // Hide toggle when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      max-width: 0;\n      opacity: 0;\n      transition:\n        max-width 0ms,\n        opacity   0ms;\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n\n    // [tablet landscape +]: Show repository information\n    @include break-from-device(tablet landscape) {\n      display: block;\n      width: px2rem(234px);\n      max-width: px2rem(234px);\n      margin-left: px2rem(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(20px);\n        margin-left: initial;\n      }\n    }\n\n    // [screen +]: Adjust spacing of search bar\n    @include break-from-device(screen) {\n      margin-left: px2rem(28px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(28px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Footer\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide footer\n  @media print {\n    display: none;\n  }\n\n  // Footer wrapper\n  &__inner {\n    padding: px2rem(4px);\n    overflow: auto;\n  }\n\n  // Footer link to previous and next page\n  &__link {\n    display: flex;\n    padding-top: px2rem(28px);\n    padding-bottom: px2rem(8px);\n    transition: opacity 250ms;\n\n    // [tablet +]: Adjust width to 50/50\n    @include break-from-device(tablet) {\n      width: 50%;\n    }\n\n    // Footer link on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Footer link to previous page\n    &--prev {\n      float: left;\n\n      // [mobile -]: Adjust width to 25/75 and hide title\n      @include break-to-device(mobile) {\n        width: 25%;\n\n        // Hide footer title\n        .md-footer__title {\n          display: none;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Footer link to next page\n    &--next {\n      float: right;\n      text-align: right;\n\n      // [mobile -]: Adjust width to 25/75\n      @include break-to-device(mobile) {\n        width: 75%;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        text-align: left;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n\n  // Footer title\n  &__title {\n    position: relative;\n    flex-grow: 1;\n    max-width: calc(100% - #{px2rem(48px)});\n    padding: 0 px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n  }\n\n  // Footer link button\n  &__button {\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n  }\n\n  // Footer link direction (i.e. prev and next)\n  &__direction {\n    position: absolute;\n    right: 0;\n    left: 0;\n    margin-top: px2rem(-20px);\n    padding: 0 px2rem(20px);\n    font-size: px2rem(12.8px);\n    opacity: 0.7;\n  }\n}\n\n// Footer metadata\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n\n  // Footer metadata wrapper\n  &__inner {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    padding: px2rem(4px);\n  }\n\n  // Lighten color for non-hovered text links\n  html &.md-typeset a {\n    color: var(--md-footer-fg-color--light);\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-footer-fg-color);\n    }\n  }\n}\n\n// Footer copyright and theme information\n.md-footer-copyright {\n  width: 100%;\n  margin: auto px2rem(12px);\n  padding: px2rem(8px) 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: px2rem(12.8px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    width: auto;\n  }\n\n  // Footer copyright highlight - this is the upper part of the copyright and\n  // theme information, which will include a darker color than the theme link\n  &__highlight {\n    color: var(--md-footer-fg-color--light);\n  }\n}\n\n// Footer social links\n.md-footer-social {\n  margin: 0 px2rem(8px);\n  padding: px2rem(4px) 0 px2rem(12px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    padding: px2rem(12px) 0;\n  }\n\n  // Footer social link\n  &__link {\n    display: inline-block;\n    width: px2rem(32px);\n    height: px2rem(32px);\n    text-align: center;\n\n    // Adjust line-height to match height for correct alignment\n    &::before {\n      line-height: 1.9;\n    }\n\n    // Fill icon with current color\n    svg {\n      max-height: px2rem(16px);\n      vertical-align: -25%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Navigation\n.md-nav {\n  font-size: px2rem(14px);\n  line-height: 1.3;\n\n  // Navigation title\n  &__title {\n    display: block;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n\n    // Navigaton button\n    .md-nav__button {\n      display: none;\n\n      // Stretch images based on height, as it's the smaller dimension\n      img {\n        width: auto;\n        height: 100%;\n      }\n\n      // Button with logo, pointing to `config.site_url`\n      &.md-logo {\n\n        // Image or icon\n        img,\n        svg {\n          display: block;\n          width: px2rem(48px);\n          height: px2rem(48px);\n          fill: currentColor;\n        }\n      }\n    }\n  }\n\n  // Navigation list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Navigation item\n  &__item {\n    padding: 0 px2rem(12px);\n\n    // Navigation item on level 2\n    & & {\n      padding-right: 0;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(12px);\n        padding-left: 0;\n      }\n    }\n  }\n\n  // Navigation link\n  &__link {\n    display: block;\n    margin-top: 0.625em;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    cursor: pointer;\n    transition: color 125ms;\n    scroll-snap-align: start;\n\n    // Link in blurred state\n    &[data-md-state=\"blur\"] {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Active link\n    .md-nav__item &--active {\n      color: var(--md-typeset-a-color);\n    }\n\n    // Navigation link in nested list\n    .md-nav__item--nested > & {\n      color: inherit;\n    }\n\n    // Navigation link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Navigation link to table of contents\n    .md-nav--primary &[for=\"__toc\"] {\n      display: none;\n\n      // Table of contents icon\n      .md-icon::after {\n        display: block;\n        width: 100%;\n        height: 100%;\n        mask-image: var(--md-toc-icon);\n        background-color: currentColor;\n      }\n\n      // Hide table of contents\n      ~ .md-nav {\n        display: none;\n      }\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Primary and nested navigation\n    &--primary,\n    &--primary & {\n      position: absolute;\n      top: 0;\n      right: 0;\n      left: 0;\n      z-index: 1;\n      display: flex;\n      flex-direction: column;\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n    }\n\n    // Primary navigation\n    &--primary {\n\n      // Navigation title and item\n      .md-nav__title,\n      .md-nav__item {\n        font-size: px2rem(16px);\n        line-height: 1.5;\n      }\n\n      // Navigation title\n      .md-nav__title {\n        position: relative;\n        height: px2rem(112px);\n        padding: px2rem(60px) px2rem(16px) px2rem(4px);\n        color: var(--md-default-fg-color--light);\n        font-weight: 400;\n        line-height: px2rem(48px);\n        white-space: nowrap;\n        background-color: var(--md-default-fg-color--lightest);\n        cursor: pointer;\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: px2rem(8px);\n          left: px2rem(8px);\n          display: block;\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin: px2rem(4px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(8px);\n            left: initial;\n          }\n\n          // Navigation icon in link to previous level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--prev);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n\n        // Navigation list\n        ~ .md-nav__list {\n          overflow-y: auto;\n          background-color: var(--md-default-bg-color);\n          box-shadow:\n            0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;\n          scroll-snap-type: y mandatory;\n          touch-action: pan-y;\n\n          // Omit border on first child\n          > :first-child {\n            border-top: 0;\n          }\n        }\n\n        // Top-level navigation title\n        &[for=\"__drawer\"] {\n          color: var(--md-primary-bg-color);\n          background-color: var(--md-primary-fg-color);\n        }\n\n        // Button with logo, pointing to `config.site_url`\n        .md-logo {\n          position: absolute;\n          top: px2rem(4px);\n          left: px2rem(4px);\n          display: block;\n          margin: px2rem(4px);\n          padding: px2rem(8px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(4px);\n            left: initial;\n          }\n        }\n      }\n\n      // Navigation list\n      .md-nav__list {\n        flex: 1;\n      }\n\n      // Navigation item\n      .md-nav__item {\n        padding: 0;\n        border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n\n        // Navigation link in nested navigation\n        &--nested > .md-nav__link {\n          padding-right: px2rem(48px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            padding-right: px2rem(16px);\n            padding-left: px2rem(48px);\n          }\n        }\n\n        // Navigation link in active navigation\n        &--active > .md-nav__link {\n          color: var(--md-typeset-a-color);\n\n          // Navigation link on focus/hover\n          &:focus,\n          &:hover {\n            color: var(--md-accent-fg-color);\n          }\n        }\n      }\n\n      // Navigation link\n      .md-nav__link {\n        position: relative;\n        margin-top: 0;\n        padding: px2rem(12px) px2rem(16px);\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: 50%;\n          right: px2rem(12px);\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin-top: px2rem(-12px);\n          color: inherit;\n          font-size: px2rem(24px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: initial;\n            left: px2rem(12px);\n          }\n\n          // Navigation icon in link to next level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--next);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n      }\n\n      // Flip icon vertically\n      .md-nav__icon {\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] &::after {\n          transform: scale(-1);\n        }\n      }\n\n      // Table of contents contained in primary navigation\n      .md-nav--secondary {\n\n        // Navigation link - omit unnecessary layering\n        .md-nav__link {\n          position: static;\n        }\n\n        // Navigation on level 2-6\n        .md-nav {\n          position: static;\n          background-color: transparent;\n\n          // Navigation link on level 3\n          .md-nav__link {\n            padding-left: px2rem(28px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(28px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 4\n          .md-nav .md-nav__link {\n            padding-left: px2rem(40px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(40px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 5\n          .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(52px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(52px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 6\n          .md-nav .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(64px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(64px);\n              padding-left: initial;\n            }\n          }\n        }\n      }\n    }\n\n    // Table of contents\n    &--secondary {\n      background-color: transparent;\n    }\n\n    // Toggle for nested navigation\n    &__toggle ~ & {\n      display: flex;\n      transform: translateX(100%);\n      opacity: 0;\n      transition:\n        transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n        opacity   125ms 50ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(-100%);\n      }\n    }\n\n    // Show nested navigation when toggle is active\n    &__toggle:checked ~ & {\n      transform: translateX(0);\n      opacity: 1;\n      transition:\n        transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   125ms 125ms;\n\n      // Navigation list\n      > .md-nav__list {\n        // Hack: promote to own layer to reduce jitter\n        backface-visibility: hidden;\n      }\n    }\n  }\n\n  // [tablet portrait -]: Layered navigation with table of contents\n  @include break-to-device(tablet portrait) {\n\n    // Show link to table of contents\n    &--primary &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n\n    // Repository information container\n    &__source {\n      display: block;\n      padding: 0 px2rem(4px);\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color--dark);\n    }\n  }\n\n  // [tablet landscape]: Layered navigation with table of contents\n  @include break-at-device(tablet landscape) {\n\n    // Show link to integrated table of contents\n    &--integrated &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n      scroll-snap-align: initial;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n  }\n\n  // [tablet landscape +]: Tree-like table of contents\n  @include break-from-device(tablet landscape) {\n\n    // Navigation title\n    &--secondary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__toc\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n  }\n\n  // [screen +]: Tree-like navigation\n  @include break-from-device(screen) {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n\n    // Navigation title\n    &--primary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__drawer\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n\n    // Hide toggle for nested navigation\n    &__toggle ~ & {\n      display: none;\n    }\n\n    // Show nested navigation when toggle is active or indeterminate\n    &__toggle:checked ~ &,\n    &__toggle:indeterminate ~ & {\n      display: block;\n    }\n\n    // Hide navigation title in nested navigation\n    &__item--nested > & > &__title {\n      display: none;\n    }\n\n    // Navigation section\n    &__item--section {\n      display: block;\n      margin: 1.25em 0;\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Hide navigation link, as sections are always expanded\n      > .md-nav__link {\n        display: none;\n      }\n\n      // Navigation\n      > .md-nav {\n        display: block;\n\n        // Navigation title\n        > .md-nav__title {\n          display: block;\n          padding: 0;\n          pointer-events: none;\n          scroll-snap-align: start;\n        }\n\n        // Adjust spacing on next level item\n        > .md-nav__list > .md-nav__item {\n          padding: 0;\n        }\n      }\n    }\n\n    // Navigation icon\n    &__icon {\n      float: right;\n      width: px2rem(18px);\n      height: px2rem(18px);\n      transition: transform 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        transform: rotate(180deg);\n      }\n\n      // Navigation icon content\n      &::after {\n        display: inline-block;\n        width: 100%;\n        height: 100%;\n        vertical-align: px2rem(-2px);\n        background-color: currentColor;\n        mask-image: var(--md-nav-icon--next);\n        mask-repeat: no-repeat;\n        mask-size: contain;\n        content: \"\";\n      }\n\n      // Navigation icon - rotate icon when toggle is active or indeterminate\n      .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,\n      .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {\n        transform: rotate(90deg);\n      }\n    }\n\n    // Modifier for when navigation tabs are rendered\n    &--lifted {\n\n      // Hide nested items on level 1 and site title\n      > .md-nav__list > .md-nav__item--nested,\n      > .md-nav__title {\n        display: none;\n      }\n\n      // Hide level 1 items\n      > .md-nav__list > .md-nav__item {\n        display: none;\n\n        // Active parent navigation item\n        &--active {\n          display: block;\n          padding: 0;\n\n          // Hide nested links\n          > .md-nav__link {\n            display: none;\n          }\n\n          // Show title and adjust spacing\n          > .md-nav > .md-nav__title {\n            display: block;\n            padding: 0 px2rem(12px);\n            pointer-events: none;\n            scroll-snap-align: start;\n          }\n        }\n\n        // Adjust spacing for navigation item on level 2\n        > .md-nav__item {\n          padding-right: px2rem(12px);\n        }\n      }\n\n      // Hack: Always show active navigation tab on breakpoint screen, despite\n      // of checkbox being checked or not. Fixes #1655.\n      .md-nav[data-md-level=\"1\"] {\n        display: block;\n      }\n    }\n\n    // Modifier for when table of contents is rendered in primary navigation\n    &--integrated &__link[for=\"__toc\"] ~ .md-nav {\n      display: block;\n      margin-bottom: 1.25em;\n      border-left: px2rem(1px) solid var(--md-primary-fg-color);\n\n      // Hide navigation title\n      > .md-nav__title {\n        display: none;\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Search\n.md-search {\n  position: relative;\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n    padding: px2rem(4px) 0;\n  }\n\n  // [no-js]: Hide search\n  .no-js & {\n    display: none;\n  }\n\n  // Search overlay\n  &__overlay {\n    z-index: 1;\n    opacity: 0;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: absolute;\n      top: px2rem(4px);\n      left: px2rem(-44px);\n      width: px2rem(40px);\n      height: px2rem(40px);\n      overflow: hidden;\n      background-color: var(--md-default-bg-color);\n      border-radius: px2rem(20px);\n      transform-origin: center;\n      transition:\n        transform 300ms 100ms,\n        opacity   200ms 200ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-44px);\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        opacity: 1;\n        transition:\n          transform 400ms,\n          opacity   100ms;\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 0;\n      height: 0;\n      background-color: hsla(0, 0%, 0%, 0.54);\n      cursor: pointer;\n      transition:\n        width     0ms 250ms,\n        height    0ms 250ms,\n        opacity 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: 0;\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        width: 100%;\n        // Hack: when the header is translated upon scrolling, a new layer is\n        // induced, which means that the height will now refer to the height of\n        // the header, albeit positioning is fixed. This should be mitigated\n        // in all cases when setting the height to 2x the viewport.\n        height: 200vh;\n        opacity: 1;\n        transition:\n          width     0ms,\n          height    0ms,\n          opacity 250ms;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [mobile portrait -]: Scale up 45 times\n      @include break-to-device(mobile portrait) {\n        transform: scale(45);\n      }\n\n      // [mobile landscape]: Scale up 60 times\n      @include break-at-device(mobile landscape) {\n        transform: scale(60);\n      }\n\n      // [tablet portrait]: Scale up 75 times\n      @include break-at-device(tablet portrait) {\n        transform: scale(75);\n      }\n    }\n  }\n\n  // Search wrapper\n  &__inner {\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n      top: 0;\n      left: 100%;\n      z-index: 2;\n      width: 100%;\n      height: 100%;\n      transform: translateX(5%);\n      opacity: 0;\n      transition:\n        right       0ms 300ms,\n        left        0ms 300ms,\n        transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   150ms 150ms;\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        left: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          right       0ms   0ms,\n          left        0ms   0ms,\n          transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms 150ms;\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: 0;\n          left: initial;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      html [dir=\"rtl\"] & {\n        right: 100%;\n        left: initial;\n        transform: translateX(-5%);\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: relative;\n      float: right;\n      width: px2rem(234px);\n      padding: px2rem(2px) 0;\n      transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [tablet landscape]: Omit overlaying header title\n      @include break-at-device(tablet landscape) {\n        width: px2rem(468px);\n      }\n\n      // [screen +]: Match width of content area\n      @include break-from-device(screen) {\n        width: px2rem(688px);\n      }\n    }\n  }\n\n  // Search form\n  &__form {\n    position: relative;\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      border-radius: px2rem(2px);\n    }\n  }\n\n  // Search input\n  &__input {\n    position: relative;\n    z-index: 2;\n    padding: 0 px2rem(44px) 0 px2rem(72px);\n    text-overflow: ellipsis;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0 px2rem(12px) transparent;\n    transition:\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: 0 px2rem(72px) 0 px2rem(44px);\n    }\n\n    // Search placeholder\n    &::placeholder {\n      transition: color 250ms;\n    }\n\n    // Search icon and placeholder\n    ~ .md-search__icon,\n    &::placeholder {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Remove the \"x\" rendered by Internet Explorer\n    &::-ms-clear {\n      display: none;\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);\n    }\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      width: 100%;\n      height: px2rem(48px);\n      font-size: px2rem(18px);\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      width: 100%;\n      height: px2rem(36px);\n      padding-left: px2rem(44px);\n      color: inherit;\n      font-size: px2rem(16px);\n      background-color: hsla(0, 0%, 0%, 0.26);\n      border-radius: px2rem(2px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n      }\n\n      // Search icon\n      + .md-search__icon {\n        color: var(--md-primary-bg-color);\n      }\n\n      // Search placeholder\n      &::placeholder {\n        color: var(--md-primary-bg-color--light);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.12);\n      }\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        color: var(--md-default-fg-color);\n        text-overflow: clip;\n        background-color: var(--md-default-bg-color);\n        border-radius: px2rem(2px) px2rem(2px) 0 0;\n\n        // Search icon and placeholder\n        + .md-search__icon,\n        &::placeholder {\n          color: var(--md-default-fg-color--light);\n        }\n      }\n    }\n  }\n\n  // Search icon\n  &__icon {\n    position: absolute;\n    z-index: 2;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    cursor: pointer;\n    transition:\n      color   250ms,\n      opacity 250ms;\n\n    // Search icon on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Search focus button\n    &[for=\"__search\"] {\n      top: px2rem(6px);\n      left: px2rem(10px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(10px);\n        left: initial;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: px2rem(16px);\n          left: initial;\n        }\n\n        // Hide the magnifying glass\n        svg:first-child {\n          display: none;\n        }\n      }\n\n      // [tablet landscape +]: Header-embedded search\n      @include break-from-device(tablet landscape) {\n        pointer-events: none;\n\n        // Hide the back arrow\n        svg:last-child {\n          display: none;\n        }\n      }\n    }\n\n    // Search reset button\n    &[type=\"reset\"] {\n      top: px2rem(6px);\n      right: px2rem(10px);\n      transform: scale(0.75);\n      opacity: 0;\n      transition:\n        transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(10px);\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        right: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: initial;\n          left: px2rem(16px);\n        }\n      }\n\n      // Show reset button when search is active and input non-empty\n      [data-md-toggle=\"search\"]:checked ~ .md-header\n      .md-search__input:valid ~ & {\n        transform: scale(1);\n        opacity: 1;\n        pointer-events: initial;\n\n        // Search focus icon\n        &:hover {\n          opacity: 0.7;\n        }\n      }\n    }\n  }\n\n  // Search output\n  &__output {\n    position: absolute;\n    z-index: 1;\n    width: 100%;\n    overflow: hidden;\n    border-radius: 0 0 px2rem(2px) px2rem(2px);\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      top: px2rem(48px);\n      bottom: 0;\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      top: px2rem(38px);\n      opacity: 0;\n      transition: opacity 400ms;\n\n      // Show output when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        @include z-depth(6);\n\n        opacity: 1;\n      }\n    }\n  }\n\n  // Search scroll wrapper\n  &__scrollwrap {\n    height: 100%;\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping\n    // seems to be something that is not ready for prime time on some browsers.\n    // scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n\n    // Mitigiate excessive repaints on non-retina devices\n    @media (max-resolution: 1dppx) {\n      transform: translateZ(0);\n    }\n\n    // [tablet landscape]: Set fixed width to omit unnecessary reflow\n    @include break-at-device(tablet landscape) {\n      width: px2rem(468px);\n    }\n\n    // [screen +]: Set fixed width to omit unnecessary reflow\n    @include break-from-device(screen) {\n      width: px2rem(688px);\n    }\n\n    // [tablet landscape +]: Limit height to viewport\n    @include break-from-device(tablet landscape) {\n      max-height: 0;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Show scroll wrapper when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        max-height: 75vh;\n      }\n\n      // Search scroll wrapper on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n}\n\n// Search result\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n\n  // Search result metadata\n  &__meta {\n    padding: 0 px2rem(16px);\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: px2rem(36px);\n    background-color: var(--md-default-fg-color--lightest);\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Search result list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Search result item\n  &__item {\n    box-shadow: 0 px2rem(-1px) 0 var(--md-default-fg-color--lightest);\n\n    // Omit border on first child\n    &:first-child {\n      box-shadow: none;\n    }\n  }\n\n  // Search result link\n  &__link {\n    display: block;\n    outline: none;\n    transition: background-color 250ms;\n    scroll-snap-align: start;\n\n    // Search result link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Adjust spacing on last child of last link\n    &:last-child p:last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Search result more link\n  &__more summary {\n    display: block;\n    padding: px2em(12px) px2rem(16px);\n    color: var(--md-typeset-a-color);\n    font-size: px2rem(12.8px);\n    outline: 0;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result more link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n\n    // Adjust transparency of less relevant results\n    ~ * > * {\n      opacity: 0.65;\n    }\n  }\n\n  // Search result article\n  &__article {\n    position: relative;\n    padding: 0 px2rem(16px);\n    overflow: hidden;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result article document\n    &--document {\n\n      // Search result title\n      .md-search-result__title {\n        margin: px2rem(11px) 0;\n        font-weight: 400;\n        font-size: px2rem(16px);\n        line-height: 1.4;\n      }\n    }\n  }\n\n  // Search result icon\n  &__icon {\n    position: absolute;\n    left: 0;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    margin: px2rem(10px);\n    color: var(--md-default-fg-color--light);\n\n    // [tablet portrait -]: Hide icon\n    @include break-to-device(tablet portrait) {\n      display: none;\n    }\n\n    // Search result icon content\n    &::after {\n      display: inline-block;\n      width: 100%;\n      height: 100%;\n      background-color: currentColor;\n      mask-image: var(--md-search-result-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: 0;\n      left: initial;\n\n      // Flip icon vertically\n      &::after {\n        transform: scaleX(-1);\n      }\n    }\n  }\n\n  // Search result title\n  &__title {\n    margin: 0.5em 0;\n    font-weight: 700;\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n  }\n\n  // Search result teaser\n  &__teaser {\n    display: -webkit-box;\n    max-height: px2rem(40px);\n    margin: 0.5em 0;\n    overflow: hidden;\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n    text-overflow: ellipsis;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 2;\n\n    // [mobile -]: Adjust number of lines\n    @include break-to-device(mobile) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // [tablet landscape]: Adjust number of lines\n    @include break-at-device(tablet landscape) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // Search term highlighting\n    mark {\n      text-decoration: underline;\n      background-color: transparent;\n    }\n  }\n\n  // Search result terms\n  &__terms {\n    margin: 0.5em 0;\n    font-size: px2rem(12.8px);\n    font-style: italic;\n  }\n\n  // Search term highlighting\n  mark {\n    color: var(--md-accent-fg-color);\n    background-color: transparent;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Selection\n.md-select {\n  position: relative;\n  z-index: 1;\n\n  // Selection bubble\n  &__inner {\n    position: absolute;\n    top: calc(100% - #{px2rem(4px)});\n    left: 50%;\n    max-height: 0;\n    margin-top: px2rem(4px);\n    color: var(--md-default-fg-color);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    transform: translate3d(-50%, px2rem(6px), 0);\n    opacity: 0;\n    transition:\n      transform  250ms 375ms,\n      opacity    250ms 250ms,\n      max-height   0ms 500ms;\n\n    // Selection bubble on parent focus/hover\n    .md-select:focus-within &,\n    .md-select:hover & {\n      max-height: px2rem(200px);\n      transform: translate3d(-50%, 0, 0);\n      opacity: 1;\n      transition:\n        transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity    250ms,\n        max-height 250ms;\n    }\n\n    // Selection bubble handle\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 50%;\n      width: 0;\n      height: 0;\n      margin-top: px2rem(-4px);\n      margin-left: px2rem(-4px);\n      border: px2rem(4px) solid transparent;\n      border-top: 0;\n      border-bottom-color: var(--md-default-bg-color);\n      content: \"\";\n    }\n  }\n\n  // Selection list\n  &__list {\n    max-height: inherit;\n    margin: 0;\n    padding: 0;\n    overflow: auto;\n    font-size: px2rem(16px);\n    list-style-type: none;\n    border-radius: px2rem(2px);\n  }\n\n  // Selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    cursor: pointer;\n    transition:\n      background-color 250ms,\n      color            250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Sidebar\n.md-sidebar {\n  position: sticky;\n  top: px2rem(48px);\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: px2rem(242px);\n  padding: px2rem(24px) 0;\n\n  // [print]: Hide sidebar\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Show navigation as drawer\n  @include break-to-device(tablet) {\n\n    // Primary sidebar with navigation\n    &--primary {\n      position: fixed;\n      top: 0;\n      left: px2rem(-242px);\n      z-index: 3;\n      display: block;\n      width: px2rem(242px);\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n      transform: translateX(0);\n      transition:\n        transform  250ms cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-242px);\n        left: initial;\n      }\n\n      // Show sidebar when drawer is active\n      [data-md-toggle=\"drawer\"]:checked ~ .md-container & {\n        @include z-depth(8);\n\n        transform: translateX(px2rem(242px));\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          transform: translateX(px2rem(-242px));\n        }\n      }\n\n      // Stretch scroll wrapper for primary sidebar\n      .md-sidebar__scrollwrap {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        margin: 0;\n        scroll-snap-type: none;\n        overflow: hidden;\n      }\n    }\n  }\n\n  // [screen +]: Show navigation as sidebar\n  @include break-from-device(screen) {\n    height: 0;\n\n    // [no-js]: Switch to native sticky behavior\n    .no-js & {\n      height: auto;\n    }\n  }\n\n  // Secondary sidebar with table of contents\n  &--secondary {\n    display: none;\n    order: 2;\n\n    // [tablet landscape +]: Show table of contents as sidebar\n    @include break-from-device(tablet landscape) {\n      height: 0;\n\n      // [no-js]: Switch to native sticky behavior\n      .no-js & {\n        height: auto;\n      }\n\n      // Sidebar is visible\n      &:not([hidden]) {\n        display: block;\n      }\n\n      // Ensure smooth scrolling on iOS\n      .md-sidebar__scrollwrap {\n        touch-action: pan-y;\n      }\n    }\n  }\n\n  // Sidebar scroll wrapper\n  &__scrollwrap {\n    margin: 0 px2rem(4px);\n    overflow-y: auto;\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container\n    // to the bottom if `scroll-snap-type` is set on the initial render. For\n    // this reason, we disable scroll snapping until this is resolved (#1667).\n    // scroll-snap-type: y mandatory;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n    // Sidebar scroll wrapper on hover\n    &:hover {\n      scrollbar-color: var(--md-accent-fg-color) transparent;\n    }\n\n    // Webkit scrollbar\n    &::-webkit-scrollbar {\n      width: px2rem(4px);\n      height: px2rem(4px);\n    }\n\n    // Webkit scrollbar thumb\n    &::-webkit-scrollbar-thumb {\n      background-color: var(--md-default-fg-color--lighter);\n\n      // Webkit scrollbar thumb on hover\n      &:hover {\n        background-color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n\n// [tablet -]: Show overlay on active drawer\n@include break-to-device(tablet) {\n\n  // Sidebar overlay\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: hsla(0, 0%, 0%, 0.54);\n    opacity: 0;\n    transition:\n      width     0ms 250ms,\n      height    0ms 250ms,\n      opacity 250ms;\n\n    // Show overlay when drawer is active\n    [data-md-toggle=\"drawer\"]:checked ~ & {\n      width: 100%;\n      height: 100%;\n      opacity: 1;\n      transition:\n        width     0ms,\n        height    0ms,\n        opacity 250ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Keyframes\n// ----------------------------------------------------------------------------\n\n// Show repository facts\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n\n  100% {\n    height: px2rem(13px);\n  }\n}\n\n// Show repository fact\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n\n  50% {\n    opacity: 0;\n  }\n\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-source-forks-icon: svg-load(\"octicons/repo-forked-16.svg\");\n  --md-source-repositories-icon: svg-load(\"octicons/repo-16.svg\");\n  --md-source-stars-icon: svg-load(\"octicons/star-16.svg\");\n  --md-source-version-icon: svg-load(\"octicons/tag-16.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Repository information\n.md-source {\n  display: block;\n  font-size: px2rem(13px);\n  line-height: 1.2;\n  white-space: nowrap;\n  // Hack: promote to own layer to reduce jitter\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n\n  // Repository information on hover\n  &:hover {\n    opacity: 0.7;\n  }\n\n  // Repository icon\n  &__icon {\n    display: inline-block;\n    width: px2rem(40px);\n    height: px2rem(48px);\n    vertical-align: middle;\n\n    // Align with margin only (as opposed to normal button alignment)\n    svg {\n      margin-top: px2rem(12px);\n      margin-left: px2rem(12px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(12px);\n        margin-left: initial;\n      }\n    }\n\n    // Adjust spacing if icon is present\n    + .md-source__repository {\n      margin-left: px2rem(-40px);\n      padding-left: px2rem(40px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(-40px);\n        margin-left: initial;\n        padding-right: px2rem(40px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Repository name\n  &__repository {\n    display: inline-block;\n    max-width: calc(100% - #{px2rem(24px)});\n    margin-left: px2rem(12px);\n    overflow: hidden;\n    text-overflow: ellipsis;\n    vertical-align: middle;\n  }\n\n  // Repository facts\n  &__facts {\n    margin: px2rem(2px) 0 0;\n    padding: 0;\n    overflow: hidden;\n    font-size: px2rem(11px);\n    list-style-type: none;\n    opacity: 0.75;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__facts--done 250ms ease-in;\n    }\n  }\n\n  // Repository fact\n  &__fact {\n    display: inline-block;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__fact--done 400ms ease-out;\n    }\n\n    // Repository fact icon\n    &::before {\n      display: inline-block;\n      width: px2rem(12px);\n      height: px2rem(12px);\n      margin-right: px2rem(2px);\n      vertical-align: text-top;\n      background-color: currentColor;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust spacing for repository fact icon\n    &:nth-child(1n+2)::before {\n      margin-left: px2rem(8px);\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: initial;\n      margin-left: px2rem(2px);\n\n      // Adjust spacing for repository fact icon\n      &:nth-child(1n+2)::before {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n\n    // Repository fact: version\n    &--version::before {\n      mask-image: var(--md-source-version-icon);\n    }\n\n    // Repository fact: stars\n    &--stars::before {\n      mask-image: var(--md-source-stars-icon);\n    }\n\n    // Repository fact: forks\n    &--forks::before {\n      mask-image: var(--md-source-forks-icon);\n    }\n\n    // Repository fact: repositories\n    &--repositories::before {\n      mask-image: var(--md-source-repositories-icon);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Navigation tabs\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n\n  // [print]: Hide tabs\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Hide tabs\n  @include break-to-device(tablet) {\n    display: none;\n  }\n\n  // Tabs in hidden state, i.e. when scrolling down\n  &[data-md-state=\"hidden\"] {\n    pointer-events: none;\n  }\n\n  // Navigation tabs list\n  &__list {\n    margin: 0;\n    margin-left: px2rem(4px);\n    padding: 0;\n    white-space: nowrap;\n    list-style: none;\n    contain: content;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(4px);\n      margin-left: initial;\n    }\n  }\n\n  // Navigation tabs item\n  &__item {\n    display: inline-block;\n    height: px2rem(48px);\n    padding-right: px2rem(12px);\n    padding-left: px2rem(12px);\n  }\n\n  // Navigation tabs link - could be defined as block elements and aligned via\n  // line height, but this would imply more repaints when scrolling\n  &__link {\n    display: block;\n    margin-top: px2rem(16px);\n    font-size: px2rem(14px);\n    // Hack: save a repaint when tabs are appearing on scrolling up\n    backface-visibility: hidden;\n    opacity: 0.7;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   250ms;\n\n    // Active link and link on focus/hover\n    &--active,\n    &:focus,\n    &:hover {\n      color: inherit;\n      opacity: 1;\n    }\n\n    // Delay transitions by a small amount\n    @for $i from 2 through 16 {\n      .md-tabs__item:nth-child(#{$i}) & {\n        transition-delay: 20ms * ($i - 1);\n      }\n    }\n\n    // Hide tabs upon scrolling - disable transition to minimizes repaints\n    // while scrolling down, while scrolling up seems to be okay\n    .md-tabs[data-md-state=\"hidden\"] & {\n      transform: translateY(50%);\n      opacity: 0;\n      transition:\n        transform 0ms 100ms,\n        opacity 100ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Back-to-top button\n.md-top {\n  position: sticky;\n  bottom: px2rem(8px);\n  z-index: 1;\n  float: right;\n  margin: px2rem(-56px) px2rem(8px) px2rem(8px);\n  padding: px2rem(8px);\n  color: var(--md-primary-bg-color);\n  background: var(--md-primary-fg-color);\n  border-radius: 100%;\n  outline: none;\n  box-shadow:\n    0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n    0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n  transform: translateY(0);\n  transition:\n    opacity          125ms,\n    transform        125ms cubic-bezier(0.4, 0, 0.2, 1),\n    background-color 125ms;\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    float: left;\n  }\n\n  // Back-to-top button in hidden state\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(px2rem(-4px));\n    opacity: 0;\n  }\n\n  // Back-to-top button on focus/hover\n  &:focus,\n  &:hover {\n    background: var(--md-accent-fg-color);\n    transform: scale(1.1);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Version selection\n.md-version {\n  flex-shrink: 0;\n  height: px2rem(48px);\n  font-size: px2rem(16px);\n\n  // Current selection\n  &__current {\n    position: relative;\n    // Hack: in general, we would use `vertical-align` to align the version at\n    // the bottom with the title, but since the list uses absolute positioning,\n    // this won't work consistently. Furthermore, we would need to use inline\n    // positioning to align the links, which looks jagged.\n    top: px2rem(1px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(28px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(28px);\n      margin-left: px2rem(8px);\n    }\n\n    // Version selection icon\n    &::after {\n      display: inline-block;\n      width: px2rem(8px);\n      height: px2rem(12px);\n      margin-left: px2rem(8px);\n      background-color: currentColor;\n      mask-image: var(--md-version-icon);\n      mask-repeat: no-repeat;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n  }\n\n  // Version selection list\n  &__list {\n    position: absolute;\n    top: px2rem(3px);\n    z-index: 1;\n    max-height: px2rem(36px);\n    margin: px2rem(4px) px2rem(16px);\n    padding: 0;\n    overflow: auto;\n    color: var(--md-default-fg-color);\n    list-style-type: none;\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    opacity: 0;\n    transition:\n      max-height 0ms 500ms,\n      opacity  250ms 250ms;\n    scroll-snap-type: y mandatory;\n\n    // List on focus/hover\n    &:focus-within,\n    &:hover {\n      max-height: px2rem(200px);\n      opacity: 1;\n      transition:\n        max-height 250ms,\n        opacity    250ms;\n    }\n  }\n\n  // Version selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Version selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    white-space: nowrap;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n/// Admonition flavours\n$admonitions: (\n  note:                       pencil $clr-blue-a200,\n  abstract summary tldr:      text-subject $clr-light-blue-a400,\n  info todo:                  information $clr-cyan-a700,\n  tip hint important:         fire $clr-teal-a700,\n  success check done:         check-circle $clr-green-a700,\n  question help faq:          help-circle $clr-light-green-a700,\n  warning caution attention:  alert $clr-orange-a400,\n  failure fail missing:       close-circle $clr-red-a200,\n  danger error:               flash-circle $clr-red-a400,\n  bug:                        bug $clr-pink-a400,\n  example:                    format-list-numbered $clr-deep-purple-a200,\n  quote cite:                 format-quote-close $clr-grey\n) !default;\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  @each $names, $props in $admonitions {\n    --md-admonition-icon--#{nth($names, 1)}:\n      svg-load(\"material/#{nth($props, 1)}.svg\");\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Admonition\n  .admonition {\n    margin: px2em(20px, 12.8px) 0;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    color: var(--md-admonition-fg-color);\n    font-size: px2rem(12.8px);\n    page-break-inside: avoid;\n    background-color: var(--md-admonition-bg-color);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.05);\n\n    // [print]: Omit shadow as it may lead to rendering errors\n    @media print {\n      box-shadow: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust vertical spacing for nested admonitions\n    .admonition {\n      margin-top: 1em;\n      margin-bottom: 1em;\n    }\n\n    // Adjust spacing for contained table wrappers\n    .md-typeset__scrollwrap {\n      margin: 1em px2rem(-12px);\n    }\n\n    // Adjust spacing for contained tables\n    .md-typeset__table {\n      padding: 0 px2rem(12px);\n    }\n\n    // Adjust spacing for single-child tabbed block container\n    > .tabbed-set:only-child {\n      margin-top: 0;\n    }\n\n    // Adjust spacing on last child\n    html & > :last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Admonition title\n  .admonition-title {\n    position: relative;\n    margin: 0 px2rem(-12px) 0 px2rem(-16px);\n    padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);\n    font-weight: 700;\n    background-color: transparentize($clr-blue-a200, 0.9);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin: 0 px2rem(-16px) 0 px2rem(-12px);\n      padding: px2rem(8px) px2rem(40px) px2rem(8px) px2rem(12px);\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for title-only admonitions\n    html &:last-child {\n      margin-bottom: 0;\n    }\n\n    // Admonition icon\n    &::before {\n      position: absolute;\n      left: px2rem(12px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: $clr-blue-a200;\n      mask-image: var(--md-admonition-icon--note);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(12px);\n        left: initial;\n      }\n    }\n\n    // Adjust spacing on last tabbed block container child - if the tabbed\n    // block container is the sole child, it looks better to omit the margin\n    + .tabbed-set:last-child {\n      margin-top: 0;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: flavours\n// ----------------------------------------------------------------------------\n\n@each $names, $props in $admonitions {\n  $name: nth($names, 1);\n  $tint: nth($props, 2);\n\n  // Admonition flavour\n  .md-typeset .admonition.#{$name} {\n    border-color: $tint;\n  }\n\n  // Admonition flavour title\n  .md-typeset .#{$name} > .admonition-title {\n    background-color: transparentize($tint, 0.9);\n    border-color: $tint;\n\n    // Admonition icon\n    &::before {\n      background-color: $tint;\n      mask-image: var(--md-admonition-icon--#{$name});\n      mask-repeat: no-repeat;\n      mask-size: contain;\n    }\n  }\n\n  // Define synonyms for flavours\n  @if length($names) > 1 {\n    @for $n from 2 through length($names) {\n      .#{nth($names, $n)} {\n        @extend .#{$name};\n      }\n    }\n  }\n}\n","// ==========================================================================\n//\n// Name:        UI Color Palette\n// Description: The color palette of material design.\n// Version:     2.3.1\n//\n// Author:      Denis Malinochkin\n// Git:         https://github.com/mrmlnc/material-color\n//\n// twitter:     @mrmlnc\n//\n// ==========================================================================\n\n\n//\n// List of base colors\n//\n\n// $clr-red\n// $clr-pink\n// $clr-purple\n// $clr-deep-purple\n// $clr-indigo\n// $clr-blue\n// $clr-light-blue\n// $clr-cyan\n// $clr-teal\n// $clr-green\n// $clr-light-green\n// $clr-lime\n// $clr-yellow\n// $clr-amber\n// $clr-orange\n// $clr-deep-orange\n// $clr-brown\n// $clr-grey\n// $clr-blue-grey\n// $clr-black\n// $clr-white\n\n\n//\n// Red\n//\n\n$clr-red-list: (\n  \"base\": #f44336,\n  \"50\":   #ffebee,\n  \"100\":  #ffcdd2,\n  \"200\":  #ef9a9a,\n  \"300\":  #e57373,\n  \"400\":  #ef5350,\n  \"500\":  #f44336,\n  \"600\":  #e53935,\n  \"700\":  #d32f2f,\n  \"800\":  #c62828,\n  \"900\":  #b71c1c,\n  \"a100\": #ff8a80,\n  \"a200\": #ff5252,\n  \"a400\": #ff1744,\n  \"a700\": #d50000\n);\n\n$clr-red:      map-get($clr-red-list, \"base\");\n\n$clr-red-50:   map-get($clr-red-list, \"50\");\n$clr-red-100:  map-get($clr-red-list, \"100\");\n$clr-red-200:  map-get($clr-red-list, \"200\");\n$clr-red-300:  map-get($clr-red-list, \"300\");\n$clr-red-400:  map-get($clr-red-list, \"400\");\n$clr-red-500:  map-get($clr-red-list, \"500\");\n$clr-red-600:  map-get($clr-red-list, \"600\");\n$clr-red-700:  map-get($clr-red-list, \"700\");\n$clr-red-800:  map-get($clr-red-list, \"800\");\n$clr-red-900:  map-get($clr-red-list, \"900\");\n$clr-red-a100: map-get($clr-red-list, \"a100\");\n$clr-red-a200: map-get($clr-red-list, \"a200\");\n$clr-red-a400: map-get($clr-red-list, \"a400\");\n$clr-red-a700: map-get($clr-red-list, \"a700\");\n\n\n//\n// Pink\n//\n\n$clr-pink-list: (\n  \"base\": #e91e63,\n  \"50\":   #fce4ec,\n  \"100\":  #f8bbd0,\n  \"200\":  #f48fb1,\n  \"300\":  #f06292,\n  \"400\":  #ec407a,\n  \"500\":  #e91e63,\n  \"600\":  #d81b60,\n  \"700\":  #c2185b,\n  \"800\":  #ad1457,\n  \"900\":  #880e4f,\n  \"a100\": #ff80ab,\n  \"a200\": #ff4081,\n  \"a400\": #f50057,\n  \"a700\": #c51162\n);\n\n$clr-pink:      map-get($clr-pink-list, \"base\");\n\n$clr-pink-50:   map-get($clr-pink-list, \"50\");\n$clr-pink-100:  map-get($clr-pink-list, \"100\");\n$clr-pink-200:  map-get($clr-pink-list, \"200\");\n$clr-pink-300:  map-get($clr-pink-list, \"300\");\n$clr-pink-400:  map-get($clr-pink-list, \"400\");\n$clr-pink-500:  map-get($clr-pink-list, \"500\");\n$clr-pink-600:  map-get($clr-pink-list, \"600\");\n$clr-pink-700:  map-get($clr-pink-list, \"700\");\n$clr-pink-800:  map-get($clr-pink-list, \"800\");\n$clr-pink-900:  map-get($clr-pink-list, \"900\");\n$clr-pink-a100: map-get($clr-pink-list, \"a100\");\n$clr-pink-a200: map-get($clr-pink-list, \"a200\");\n$clr-pink-a400: map-get($clr-pink-list, \"a400\");\n$clr-pink-a700: map-get($clr-pink-list, \"a700\");\n\n\n//\n// Purple\n//\n\n$clr-purple-list: (\n  \"base\": #9c27b0,\n  \"50\":   #f3e5f5,\n  \"100\":  #e1bee7,\n  \"200\":  #ce93d8,\n  \"300\":  #ba68c8,\n  \"400\":  #ab47bc,\n  \"500\":  #9c27b0,\n  \"600\":  #8e24aa,\n  \"700\":  #7b1fa2,\n  \"800\":  #6a1b9a,\n  \"900\":  #4a148c,\n  \"a100\": #ea80fc,\n  \"a200\": #e040fb,\n  \"a400\": #d500f9,\n  \"a700\": #aa00ff\n);\n\n$clr-purple:      map-get($clr-purple-list, \"base\");\n\n$clr-purple-50:   map-get($clr-purple-list, \"50\");\n$clr-purple-100:  map-get($clr-purple-list, \"100\");\n$clr-purple-200:  map-get($clr-purple-list, \"200\");\n$clr-purple-300:  map-get($clr-purple-list, \"300\");\n$clr-purple-400:  map-get($clr-purple-list, \"400\");\n$clr-purple-500:  map-get($clr-purple-list, \"500\");\n$clr-purple-600:  map-get($clr-purple-list, \"600\");\n$clr-purple-700:  map-get($clr-purple-list, \"700\");\n$clr-purple-800:  map-get($clr-purple-list, \"800\");\n$clr-purple-900:  map-get($clr-purple-list, \"900\");\n$clr-purple-a100: map-get($clr-purple-list, \"a100\");\n$clr-purple-a200: map-get($clr-purple-list, \"a200\");\n$clr-purple-a400: map-get($clr-purple-list, \"a400\");\n$clr-purple-a700: map-get($clr-purple-list, \"a700\");\n\n\n//\n// Deep purple\n//\n\n$clr-deep-purple-list: (\n  \"base\": #673ab7,\n  \"50\":   #ede7f6,\n  \"100\":  #d1c4e9,\n  \"200\":  #b39ddb,\n  \"300\":  #9575cd,\n  \"400\":  #7e57c2,\n  \"500\":  #673ab7,\n  \"600\":  #5e35b1,\n  \"700\":  #512da8,\n  \"800\":  #4527a0,\n  \"900\":  #311b92,\n  \"a100\": #b388ff,\n  \"a200\": #7c4dff,\n  \"a400\": #651fff,\n  \"a700\": #6200ea\n);\n\n$clr-deep-purple:      map-get($clr-deep-purple-list, \"base\");\n\n$clr-deep-purple-50:   map-get($clr-deep-purple-list, \"50\");\n$clr-deep-purple-100:  map-get($clr-deep-purple-list, \"100\");\n$clr-deep-purple-200:  map-get($clr-deep-purple-list, \"200\");\n$clr-deep-purple-300:  map-get($clr-deep-purple-list, \"300\");\n$clr-deep-purple-400:  map-get($clr-deep-purple-list, \"400\");\n$clr-deep-purple-500:  map-get($clr-deep-purple-list, \"500\");\n$clr-deep-purple-600:  map-get($clr-deep-purple-list, \"600\");\n$clr-deep-purple-700:  map-get($clr-deep-purple-list, \"700\");\n$clr-deep-purple-800:  map-get($clr-deep-purple-list, \"800\");\n$clr-deep-purple-900:  map-get($clr-deep-purple-list, \"900\");\n$clr-deep-purple-a100: map-get($clr-deep-purple-list, \"a100\");\n$clr-deep-purple-a200: map-get($clr-deep-purple-list, \"a200\");\n$clr-deep-purple-a400: map-get($clr-deep-purple-list, \"a400\");\n$clr-deep-purple-a700: map-get($clr-deep-purple-list, \"a700\");\n\n\n//\n// Indigo\n//\n\n$clr-indigo-list: (\n  \"base\": #3f51b5,\n  \"50\":   #e8eaf6,\n  \"100\":  #c5cae9,\n  \"200\":  #9fa8da,\n  \"300\":  #7986cb,\n  \"400\":  #5c6bc0,\n  \"500\":  #3f51b5,\n  \"600\":  #3949ab,\n  \"700\":  #303f9f,\n  \"800\":  #283593,\n  \"900\":  #1a237e,\n  \"a100\": #8c9eff,\n  \"a200\": #536dfe,\n  \"a400\": #3d5afe,\n  \"a700\": #304ffe\n);\n\n$clr-indigo:      map-get($clr-indigo-list, \"base\");\n\n$clr-indigo-50:   map-get($clr-indigo-list, \"50\");\n$clr-indigo-100:  map-get($clr-indigo-list, \"100\");\n$clr-indigo-200:  map-get($clr-indigo-list, \"200\");\n$clr-indigo-300:  map-get($clr-indigo-list, \"300\");\n$clr-indigo-400:  map-get($clr-indigo-list, \"400\");\n$clr-indigo-500:  map-get($clr-indigo-list, \"500\");\n$clr-indigo-600:  map-get($clr-indigo-list, \"600\");\n$clr-indigo-700:  map-get($clr-indigo-list, \"700\");\n$clr-indigo-800:  map-get($clr-indigo-list, \"800\");\n$clr-indigo-900:  map-get($clr-indigo-list, \"900\");\n$clr-indigo-a100: map-get($clr-indigo-list, \"a100\");\n$clr-indigo-a200: map-get($clr-indigo-list, \"a200\");\n$clr-indigo-a400: map-get($clr-indigo-list, \"a400\");\n$clr-indigo-a700: map-get($clr-indigo-list, \"a700\");\n\n\n//\n// Blue\n//\n\n$clr-blue-list: (\n  \"base\": #2196f3,\n  \"50\":   #e3f2fd,\n  \"100\":  #bbdefb,\n  \"200\":  #90caf9,\n  \"300\":  #64b5f6,\n  \"400\":  #42a5f5,\n  \"500\":  #2196f3,\n  \"600\":  #1e88e5,\n  \"700\":  #1976d2,\n  \"800\":  #1565c0,\n  \"900\":  #0d47a1,\n  \"a100\": #82b1ff,\n  \"a200\": #448aff,\n  \"a400\": #2979ff,\n  \"a700\": #2962ff\n);\n\n$clr-blue:      map-get($clr-blue-list, \"base\");\n\n$clr-blue-50:   map-get($clr-blue-list, \"50\");\n$clr-blue-100:  map-get($clr-blue-list, \"100\");\n$clr-blue-200:  map-get($clr-blue-list, \"200\");\n$clr-blue-300:  map-get($clr-blue-list, \"300\");\n$clr-blue-400:  map-get($clr-blue-list, \"400\");\n$clr-blue-500:  map-get($clr-blue-list, \"500\");\n$clr-blue-600:  map-get($clr-blue-list, \"600\");\n$clr-blue-700:  map-get($clr-blue-list, \"700\");\n$clr-blue-800:  map-get($clr-blue-list, \"800\");\n$clr-blue-900:  map-get($clr-blue-list, \"900\");\n$clr-blue-a100: map-get($clr-blue-list, \"a100\");\n$clr-blue-a200: map-get($clr-blue-list, \"a200\");\n$clr-blue-a400: map-get($clr-blue-list, \"a400\");\n$clr-blue-a700: map-get($clr-blue-list, \"a700\");\n\n\n//\n// Light Blue\n//\n\n$clr-light-blue-list: (\n  \"base\": #03a9f4,\n  \"50\":   #e1f5fe,\n  \"100\":  #b3e5fc,\n  \"200\":  #81d4fa,\n  \"300\":  #4fc3f7,\n  \"400\":  #29b6f6,\n  \"500\":  #03a9f4,\n  \"600\":  #039be5,\n  \"700\":  #0288d1,\n  \"800\":  #0277bd,\n  \"900\":  #01579b,\n  \"a100\": #80d8ff,\n  \"a200\": #40c4ff,\n  \"a400\": #00b0ff,\n  \"a700\": #0091ea\n);\n\n$clr-light-blue:      map-get($clr-light-blue-list, \"base\");\n\n$clr-light-blue-50:   map-get($clr-light-blue-list, \"50\");\n$clr-light-blue-100:  map-get($clr-light-blue-list, \"100\");\n$clr-light-blue-200:  map-get($clr-light-blue-list, \"200\");\n$clr-light-blue-300:  map-get($clr-light-blue-list, \"300\");\n$clr-light-blue-400:  map-get($clr-light-blue-list, \"400\");\n$clr-light-blue-500:  map-get($clr-light-blue-list, \"500\");\n$clr-light-blue-600:  map-get($clr-light-blue-list, \"600\");\n$clr-light-blue-700:  map-get($clr-light-blue-list, \"700\");\n$clr-light-blue-800:  map-get($clr-light-blue-list, \"800\");\n$clr-light-blue-900:  map-get($clr-light-blue-list, \"900\");\n$clr-light-blue-a100: map-get($clr-light-blue-list, \"a100\");\n$clr-light-blue-a200: map-get($clr-light-blue-list, \"a200\");\n$clr-light-blue-a400: map-get($clr-light-blue-list, \"a400\");\n$clr-light-blue-a700: map-get($clr-light-blue-list, \"a700\");\n\n\n//\n// Cyan\n//\n\n$clr-cyan-list: (\n  \"base\": #00bcd4,\n  \"50\":   #e0f7fa,\n  \"100\":  #b2ebf2,\n  \"200\":  #80deea,\n  \"300\":  #4dd0e1,\n  \"400\":  #26c6da,\n  \"500\":  #00bcd4,\n  \"600\":  #00acc1,\n  \"700\":  #0097a7,\n  \"800\":  #00838f,\n  \"900\":  #006064,\n  \"a100\": #84ffff,\n  \"a200\": #18ffff,\n  \"a400\": #00e5ff,\n  \"a700\": #00b8d4\n);\n\n$clr-cyan:      map-get($clr-cyan-list, \"base\");\n\n$clr-cyan-50:   map-get($clr-cyan-list, \"50\");\n$clr-cyan-100:  map-get($clr-cyan-list, \"100\");\n$clr-cyan-200:  map-get($clr-cyan-list, \"200\");\n$clr-cyan-300:  map-get($clr-cyan-list, \"300\");\n$clr-cyan-400:  map-get($clr-cyan-list, \"400\");\n$clr-cyan-500:  map-get($clr-cyan-list, \"500\");\n$clr-cyan-600:  map-get($clr-cyan-list, \"600\");\n$clr-cyan-700:  map-get($clr-cyan-list, \"700\");\n$clr-cyan-800:  map-get($clr-cyan-list, \"800\");\n$clr-cyan-900:  map-get($clr-cyan-list, \"900\");\n$clr-cyan-a100: map-get($clr-cyan-list, \"a100\");\n$clr-cyan-a200: map-get($clr-cyan-list, \"a200\");\n$clr-cyan-a400: map-get($clr-cyan-list, \"a400\");\n$clr-cyan-a700: map-get($clr-cyan-list, \"a700\");\n\n\n//\n// Teal\n//\n\n$clr-teal-list: (\n  \"base\": #009688,\n  \"50\":   #e0f2f1,\n  \"100\":  #b2dfdb,\n  \"200\":  #80cbc4,\n  \"300\":  #4db6ac,\n  \"400\":  #26a69a,\n  \"500\":  #009688,\n  \"600\":  #00897b,\n  \"700\":  #00796b,\n  \"800\":  #00695c,\n  \"900\":  #004d40,\n  \"a100\": #a7ffeb,\n  \"a200\": #64ffda,\n  \"a400\": #1de9b6,\n  \"a700\": #00bfa5\n);\n\n$clr-teal:      map-get($clr-teal-list, \"base\");\n\n$clr-teal-50:   map-get($clr-teal-list, \"50\");\n$clr-teal-100:  map-get($clr-teal-list, \"100\");\n$clr-teal-200:  map-get($clr-teal-list, \"200\");\n$clr-teal-300:  map-get($clr-teal-list, \"300\");\n$clr-teal-400:  map-get($clr-teal-list, \"400\");\n$clr-teal-500:  map-get($clr-teal-list, \"500\");\n$clr-teal-600:  map-get($clr-teal-list, \"600\");\n$clr-teal-700:  map-get($clr-teal-list, \"700\");\n$clr-teal-800:  map-get($clr-teal-list, \"800\");\n$clr-teal-900:  map-get($clr-teal-list, \"900\");\n$clr-teal-a100: map-get($clr-teal-list, \"a100\");\n$clr-teal-a200: map-get($clr-teal-list, \"a200\");\n$clr-teal-a400: map-get($clr-teal-list, \"a400\");\n$clr-teal-a700: map-get($clr-teal-list, \"a700\");\n\n\n//\n// Green\n//\n\n$clr-green-list: (\n  \"base\": #4caf50,\n  \"50\":   #e8f5e9,\n  \"100\":  #c8e6c9,\n  \"200\":  #a5d6a7,\n  \"300\":  #81c784,\n  \"400\":  #66bb6a,\n  \"500\":  #4caf50,\n  \"600\":  #43a047,\n  \"700\":  #388e3c,\n  \"800\":  #2e7d32,\n  \"900\":  #1b5e20,\n  \"a100\": #b9f6ca,\n  \"a200\": #69f0ae,\n  \"a400\": #00e676,\n  \"a700\": #00c853\n);\n\n$clr-green:      map-get($clr-green-list, \"base\");\n\n$clr-green-50:   map-get($clr-green-list, \"50\");\n$clr-green-100:  map-get($clr-green-list, \"100\");\n$clr-green-200:  map-get($clr-green-list, \"200\");\n$clr-green-300:  map-get($clr-green-list, \"300\");\n$clr-green-400:  map-get($clr-green-list, \"400\");\n$clr-green-500:  map-get($clr-green-list, \"500\");\n$clr-green-600:  map-get($clr-green-list, \"600\");\n$clr-green-700:  map-get($clr-green-list, \"700\");\n$clr-green-800:  map-get($clr-green-list, \"800\");\n$clr-green-900:  map-get($clr-green-list, \"900\");\n$clr-green-a100: map-get($clr-green-list, \"a100\");\n$clr-green-a200: map-get($clr-green-list, \"a200\");\n$clr-green-a400: map-get($clr-green-list, \"a400\");\n$clr-green-a700: map-get($clr-green-list, \"a700\");\n\n\n//\n// Light green\n//\n\n$clr-light-green-list: (\n  \"base\": #8bc34a,\n  \"50\":   #f1f8e9,\n  \"100\":  #dcedc8,\n  \"200\":  #c5e1a5,\n  \"300\":  #aed581,\n  \"400\":  #9ccc65,\n  \"500\":  #8bc34a,\n  \"600\":  #7cb342,\n  \"700\":  #689f38,\n  \"800\":  #558b2f,\n  \"900\":  #33691e,\n  \"a100\": #ccff90,\n  \"a200\": #b2ff59,\n  \"a400\": #76ff03,\n  \"a700\": #64dd17\n);\n\n$clr-light-green:      map-get($clr-light-green-list, \"base\");\n\n$clr-light-green-50:   map-get($clr-light-green-list, \"50\");\n$clr-light-green-100:  map-get($clr-light-green-list, \"100\");\n$clr-light-green-200:  map-get($clr-light-green-list, \"200\");\n$clr-light-green-300:  map-get($clr-light-green-list, \"300\");\n$clr-light-green-400:  map-get($clr-light-green-list, \"400\");\n$clr-light-green-500:  map-get($clr-light-green-list, \"500\");\n$clr-light-green-600:  map-get($clr-light-green-list, \"600\");\n$clr-light-green-700:  map-get($clr-light-green-list, \"700\");\n$clr-light-green-800:  map-get($clr-light-green-list, \"800\");\n$clr-light-green-900:  map-get($clr-light-green-list, \"900\");\n$clr-light-green-a100: map-get($clr-light-green-list, \"a100\");\n$clr-light-green-a200: map-get($clr-light-green-list, \"a200\");\n$clr-light-green-a400: map-get($clr-light-green-list, \"a400\");\n$clr-light-green-a700: map-get($clr-light-green-list, \"a700\");\n\n\n//\n// Lime\n//\n\n$clr-lime-list: (\n  \"base\": #cddc39,\n  \"50\":   #f9fbe7,\n  \"100\":  #f0f4c3,\n  \"200\":  #e6ee9c,\n  \"300\":  #dce775,\n  \"400\":  #d4e157,\n  \"500\":  #cddc39,\n  \"600\":  #c0ca33,\n  \"700\":  #afb42b,\n  \"800\":  #9e9d24,\n  \"900\":  #827717,\n  \"a100\": #f4ff81,\n  \"a200\": #eeff41,\n  \"a400\": #c6ff00,\n  \"a700\": #aeea00\n);\n\n$clr-lime:      map-get($clr-lime-list, \"base\");\n\n$clr-lime-50:   map-get($clr-lime-list, \"50\");\n$clr-lime-100:  map-get($clr-lime-list, \"100\");\n$clr-lime-200:  map-get($clr-lime-list, \"200\");\n$clr-lime-300:  map-get($clr-lime-list, \"300\");\n$clr-lime-400:  map-get($clr-lime-list, \"400\");\n$clr-lime-500:  map-get($clr-lime-list, \"500\");\n$clr-lime-600:  map-get($clr-lime-list, \"600\");\n$clr-lime-700:  map-get($clr-lime-list, \"700\");\n$clr-lime-800:  map-get($clr-lime-list, \"800\");\n$clr-lime-900:  map-get($clr-lime-list, \"900\");\n$clr-lime-a100: map-get($clr-lime-list, \"a100\");\n$clr-lime-a200: map-get($clr-lime-list, \"a200\");\n$clr-lime-a400: map-get($clr-lime-list, \"a400\");\n$clr-lime-a700: map-get($clr-lime-list, \"a700\");\n\n\n//\n// Yellow\n//\n\n$clr-yellow-list: (\n  \"base\": #ffeb3b,\n  \"50\":   #fffde7,\n  \"100\":  #fff9c4,\n  \"200\":  #fff59d,\n  \"300\":  #fff176,\n  \"400\":  #ffee58,\n  \"500\":  #ffeb3b,\n  \"600\":  #fdd835,\n  \"700\":  #fbc02d,\n  \"800\":  #f9a825,\n  \"900\":  #f57f17,\n  \"a100\": #ffff8d,\n  \"a200\": #ffff00,\n  \"a400\": #ffea00,\n  \"a700\": #ffd600\n);\n\n$clr-yellow:      map-get($clr-yellow-list, \"base\");\n\n$clr-yellow-50:   map-get($clr-yellow-list, \"50\");\n$clr-yellow-100:  map-get($clr-yellow-list, \"100\");\n$clr-yellow-200:  map-get($clr-yellow-list, \"200\");\n$clr-yellow-300:  map-get($clr-yellow-list, \"300\");\n$clr-yellow-400:  map-get($clr-yellow-list, \"400\");\n$clr-yellow-500:  map-get($clr-yellow-list, \"500\");\n$clr-yellow-600:  map-get($clr-yellow-list, \"600\");\n$clr-yellow-700:  map-get($clr-yellow-list, \"700\");\n$clr-yellow-800:  map-get($clr-yellow-list, \"800\");\n$clr-yellow-900:  map-get($clr-yellow-list, \"900\");\n$clr-yellow-a100: map-get($clr-yellow-list, \"a100\");\n$clr-yellow-a200: map-get($clr-yellow-list, \"a200\");\n$clr-yellow-a400: map-get($clr-yellow-list, \"a400\");\n$clr-yellow-a700: map-get($clr-yellow-list, \"a700\");\n\n\n//\n// amber\n//\n\n$clr-amber-list: (\n  \"base\": #ffc107,\n  \"50\":   #fff8e1,\n  \"100\":  #ffecb3,\n  \"200\":  #ffe082,\n  \"300\":  #ffd54f,\n  \"400\":  #ffca28,\n  \"500\":  #ffc107,\n  \"600\":  #ffb300,\n  \"700\":  #ffa000,\n  \"800\":  #ff8f00,\n  \"900\":  #ff6f00,\n  \"a100\": #ffe57f,\n  \"a200\": #ffd740,\n  \"a400\": #ffc400,\n  \"a700\": #ffab00\n);\n\n$clr-amber:      map-get($clr-amber-list, \"base\");\n\n$clr-amber-50:   map-get($clr-amber-list, \"50\");\n$clr-amber-100:  map-get($clr-amber-list, \"100\");\n$clr-amber-200:  map-get($clr-amber-list, \"200\");\n$clr-amber-300:  map-get($clr-amber-list, \"300\");\n$clr-amber-400:  map-get($clr-amber-list, \"400\");\n$clr-amber-500:  map-get($clr-amber-list, \"500\");\n$clr-amber-600:  map-get($clr-amber-list, \"600\");\n$clr-amber-700:  map-get($clr-amber-list, \"700\");\n$clr-amber-800:  map-get($clr-amber-list, \"800\");\n$clr-amber-900:  map-get($clr-amber-list, \"900\");\n$clr-amber-a100: map-get($clr-amber-list, \"a100\");\n$clr-amber-a200: map-get($clr-amber-list, \"a200\");\n$clr-amber-a400: map-get($clr-amber-list, \"a400\");\n$clr-amber-a700: map-get($clr-amber-list, \"a700\");\n\n\n//\n// Orange\n//\n\n$clr-orange-list: (\n  \"base\": #ff9800,\n  \"50\":   #fff3e0,\n  \"100\":  #ffe0b2,\n  \"200\":  #ffcc80,\n  \"300\":  #ffb74d,\n  \"400\":  #ffa726,\n  \"500\":  #ff9800,\n  \"600\":  #fb8c00,\n  \"700\":  #f57c00,\n  \"800\":  #ef6c00,\n  \"900\":  #e65100,\n  \"a100\": #ffd180,\n  \"a200\": #ffab40,\n  \"a400\": #ff9100,\n  \"a700\": #ff6d00\n);\n\n$clr-orange:      map-get($clr-orange-list, \"base\");\n\n$clr-orange-50:   map-get($clr-orange-list, \"50\");\n$clr-orange-100:  map-get($clr-orange-list, \"100\");\n$clr-orange-200:  map-get($clr-orange-list, \"200\");\n$clr-orange-300:  map-get($clr-orange-list, \"300\");\n$clr-orange-400:  map-get($clr-orange-list, \"400\");\n$clr-orange-500:  map-get($clr-orange-list, \"500\");\n$clr-orange-600:  map-get($clr-orange-list, \"600\");\n$clr-orange-700:  map-get($clr-orange-list, \"700\");\n$clr-orange-800:  map-get($clr-orange-list, \"800\");\n$clr-orange-900:  map-get($clr-orange-list, \"900\");\n$clr-orange-a100: map-get($clr-orange-list, \"a100\");\n$clr-orange-a200: map-get($clr-orange-list, \"a200\");\n$clr-orange-a400: map-get($clr-orange-list, \"a400\");\n$clr-orange-a700: map-get($clr-orange-list, \"a700\");\n\n\n//\n// Deep orange\n//\n\n$clr-deep-orange-list: (\n  \"base\": #ff5722,\n  \"50\":   #fbe9e7,\n  \"100\":  #ffccbc,\n  \"200\":  #ffab91,\n  \"300\":  #ff8a65,\n  \"400\":  #ff7043,\n  \"500\":  #ff5722,\n  \"600\":  #f4511e,\n  \"700\":  #e64a19,\n  \"800\":  #d84315,\n  \"900\":  #bf360c,\n  \"a100\": #ff9e80,\n  \"a200\": #ff6e40,\n  \"a400\": #ff3d00,\n  \"a700\": #dd2c00\n);\n\n$clr-deep-orange:      map-get($clr-deep-orange-list, \"base\");\n\n$clr-deep-orange-50:   map-get($clr-deep-orange-list, \"50\");\n$clr-deep-orange-100:  map-get($clr-deep-orange-list, \"100\");\n$clr-deep-orange-200:  map-get($clr-deep-orange-list, \"200\");\n$clr-deep-orange-300:  map-get($clr-deep-orange-list, \"300\");\n$clr-deep-orange-400:  map-get($clr-deep-orange-list, \"400\");\n$clr-deep-orange-500:  map-get($clr-deep-orange-list, \"500\");\n$clr-deep-orange-600:  map-get($clr-deep-orange-list, \"600\");\n$clr-deep-orange-700:  map-get($clr-deep-orange-list, \"700\");\n$clr-deep-orange-800:  map-get($clr-deep-orange-list, \"800\");\n$clr-deep-orange-900:  map-get($clr-deep-orange-list, \"900\");\n$clr-deep-orange-a100: map-get($clr-deep-orange-list, \"a100\");\n$clr-deep-orange-a200: map-get($clr-deep-orange-list, \"a200\");\n$clr-deep-orange-a400: map-get($clr-deep-orange-list, \"a400\");\n$clr-deep-orange-a700: map-get($clr-deep-orange-list, \"a700\");\n\n\n//\n// Brown\n//\n\n$clr-brown-list: (\n  \"base\": #795548,\n  \"50\":   #efebe9,\n  \"100\":  #d7ccc8,\n  \"200\":  #bcaaa4,\n  \"300\":  #a1887f,\n  \"400\":  #8d6e63,\n  \"500\":  #795548,\n  \"600\":  #6d4c41,\n  \"700\":  #5d4037,\n  \"800\":  #4e342e,\n  \"900\":  #3e2723,\n);\n\n$clr-brown:     map-get($clr-brown-list, \"base\");\n\n$clr-brown-50:  map-get($clr-brown-list, \"50\");\n$clr-brown-100: map-get($clr-brown-list, \"100\");\n$clr-brown-200: map-get($clr-brown-list, \"200\");\n$clr-brown-300: map-get($clr-brown-list, \"300\");\n$clr-brown-400: map-get($clr-brown-list, \"400\");\n$clr-brown-500: map-get($clr-brown-list, \"500\");\n$clr-brown-600: map-get($clr-brown-list, \"600\");\n$clr-brown-700: map-get($clr-brown-list, \"700\");\n$clr-brown-800: map-get($clr-brown-list, \"800\");\n$clr-brown-900: map-get($clr-brown-list, \"900\");\n\n\n//\n// Grey\n//\n\n$clr-grey-list: (\n  \"base\": #9e9e9e,\n  \"50\":   #fafafa,\n  \"100\":  #f5f5f5,\n  \"200\":  #eeeeee,\n  \"300\":  #e0e0e0,\n  \"400\":  #bdbdbd,\n  \"500\":  #9e9e9e,\n  \"600\":  #757575,\n  \"700\":  #616161,\n  \"800\":  #424242,\n  \"900\":  #212121,\n);\n\n$clr-grey:     map-get($clr-grey-list, \"base\");\n\n$clr-grey-50:  map-get($clr-grey-list, \"50\");\n$clr-grey-100: map-get($clr-grey-list, \"100\");\n$clr-grey-200: map-get($clr-grey-list, \"200\");\n$clr-grey-300: map-get($clr-grey-list, \"300\");\n$clr-grey-400: map-get($clr-grey-list, \"400\");\n$clr-grey-500: map-get($clr-grey-list, \"500\");\n$clr-grey-600: map-get($clr-grey-list, \"600\");\n$clr-grey-700: map-get($clr-grey-list, \"700\");\n$clr-grey-800: map-get($clr-grey-list, \"800\");\n$clr-grey-900: map-get($clr-grey-list, \"900\");\n\n\n//\n// Blue grey\n//\n\n$clr-blue-grey-list: (\n  \"base\": #607d8b,\n  \"50\":   #eceff1,\n  \"100\":  #cfd8dc,\n  \"200\":  #b0bec5,\n  \"300\":  #90a4ae,\n  \"400\":  #78909c,\n  \"500\":  #607d8b,\n  \"600\":  #546e7a,\n  \"700\":  #455a64,\n  \"800\":  #37474f,\n  \"900\":  #263238,\n);\n\n$clr-blue-grey:     map-get($clr-blue-grey-list, \"base\");\n\n$clr-blue-grey-50:  map-get($clr-blue-grey-list, \"50\");\n$clr-blue-grey-100: map-get($clr-blue-grey-list, \"100\");\n$clr-blue-grey-200: map-get($clr-blue-grey-list, \"200\");\n$clr-blue-grey-300: map-get($clr-blue-grey-list, \"300\");\n$clr-blue-grey-400: map-get($clr-blue-grey-list, \"400\");\n$clr-blue-grey-500: map-get($clr-blue-grey-list, \"500\");\n$clr-blue-grey-600: map-get($clr-blue-grey-list, \"600\");\n$clr-blue-grey-700: map-get($clr-blue-grey-list, \"700\");\n$clr-blue-grey-800: map-get($clr-blue-grey-list, \"800\");\n$clr-blue-grey-900: map-get($clr-blue-grey-list, \"900\");\n\n\n//\n// Black\n//\n\n$clr-black-list: (\n  \"base\": #000\n);\n\n$clr-black: map-get($clr-black-list, \"base\");\n\n\n//\n// White\n//\n\n$clr-white-list: (\n  \"base\": #fff\n);\n\n$clr-white: map-get($clr-white-list, \"base\");\n\n\n//\n// List for all Colors for looping\n//\n\n$clr-list-all: (\n  \"red\":         $clr-red-list,\n  \"pink\":        $clr-pink-list,\n  \"purple\":      $clr-purple-list,\n  \"deep-purple\": $clr-deep-purple-list,\n  \"indigo\":      $clr-indigo-list,\n  \"blue\":        $clr-blue-list,\n  \"light-blue\":  $clr-light-blue-list,\n  \"cyan\":        $clr-cyan-list,\n  \"teal\":        $clr-teal-list,\n  \"green\":       $clr-green-list,\n  \"light-green\": $clr-light-green-list,\n  \"lime\":        $clr-lime-list,\n  \"yellow\":      $clr-yellow-list,\n  \"amber\":       $clr-amber-list,\n  \"orange\":      $clr-orange-list,\n  \"deep-orange\": $clr-deep-orange-list,\n  \"brown\":       $clr-brown-list,\n  \"grey\":        $clr-grey-list,\n  \"blue-grey\":   $clr-blue-grey-list,\n  \"black\":       $clr-black-list,\n  \"white\":       $clr-white-list\n);\n\n\n//\n// Typography\n//\n\n$clr-ui-display-4: $clr-grey-600;\n$clr-ui-display-3: $clr-grey-600;\n$clr-ui-display-2: $clr-grey-600;\n$clr-ui-display-1: $clr-grey-600;\n$clr-ui-headline:  $clr-grey-900;\n$clr-ui-title:     $clr-grey-900;\n$clr-ui-subhead-1: $clr-grey-900;\n$clr-ui-body-2:    $clr-grey-900;\n$clr-ui-body-1:    $clr-grey-900;\n$clr-ui-caption:   $clr-grey-600;\n$clr-ui-menu:      $clr-grey-900;\n$clr-ui-button:    $clr-grey-900;\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Footnote reference\n  [id^=\"fnref:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 4px);\n    padding-top: px2rem(48px + 24px - 4px);\n  }\n\n  // Footnote\n  [id^=\"fn:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 3px);\n    padding-top: px2rem(48px + 24px - 3px);\n  }\n\n  // Footnote container\n  .footnote {\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n\n    // Footnote list - omit left indentation\n    > ol {\n      margin-left: 0;\n\n      // Footnote item - footnote items can contain lists, so we need to scope\n      // the spacing adjustments to the top-level footnote item.\n      > li {\n        transition: color 125ms;\n\n        // Darken color on target\n        &:target {\n          color: var(--md-default-fg-color);\n        }\n\n        // Show backreferences on footnote hover\n        &:hover  .footnote-backref,\n        &:target .footnote-backref {\n          transform: translateX(0);\n          opacity: 1;\n        }\n\n        // Adjust spacing on first child\n        > :first-child {\n          margin-top: 0;\n        }\n      }\n    }\n  }\n\n  // Footnote backreference\n  .footnote-backref {\n    display: inline-block;\n    color: var(--md-typeset-a-color);\n    // Hack: omit Unicode arrow for replacement with icon\n    font-size: 0;\n    vertical-align: text-bottom;\n    transform: translateX(px2rem(5px));\n    opacity: 0;\n    transition:\n      color     250ms,\n      transform 250ms 250ms,\n      opacity   125ms 250ms;\n\n    // [print]: Show footnote backreferences\n    @media print {\n      color: var(--md-typeset-a-color);\n      transform: translateX(0);\n      opacity: 1;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      transform: translateX(px2rem(-5px));\n    }\n\n    // Adjust color on hover\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Footnote backreference icon\n    &::before {\n      display: inline-block;\n      width: px2rem(16px);\n      height: px2rem(16px);\n      background-color: currentColor;\n      mask-image: var(--md-footnotes-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Headerlink\n  .headerlink {\n    display: inline-block;\n    margin-left: px2rem(10px);\n    color: var(--md-default-fg-color--lighter);\n    opacity: 0;\n    transition:\n      color      250ms,\n      opacity    125ms;\n\n    // [print]: Hide headerlinks\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(10px);\n      margin-left: initial;\n    }\n  }\n\n  // Show headerlinks on parent hover\n  :hover  > .headerlink,\n  :target > .headerlink,\n  .headerlink:focus {\n    opacity: 1;\n    transition:\n      color      250ms,\n      opacity    125ms;\n  }\n\n  // Adjust color on parent target or focus/hover\n  :target > .headerlink,\n  .headerlink:focus,\n  .headerlink:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Adjust scroll offset for all elements with `id` attributes - general scroll\n  // margin offset for anything that can be targeted. Browser support is pretty\n  // decent by now, but Edge <79 and Safari (iOS and macOS) still don't support\n  // it properly, so we settle with a cross-browser anchor correction solution.\n  :target {\n    scroll-margin-top: px2rem(48px + 24px);\n  }\n\n  // Adjust scroll offset for headlines of level 1-3\n  h1:target,\n  h2:target,\n  h3:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 4px);\n      padding-top: px2rem(48px + 24px - 4px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 4\n  h4:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 3px);\n      padding-top: px2rem(48px + 24px - 3px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 5-6\n  h5:target,\n  h6:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px);\n      padding-top: px2rem(48px + 24px);\n      content: \"\";\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Arithmatex container\n  div.arithmatex {\n    overflow: auto;\n\n    // [mobile -]: Align with body copy\n    @include break-to-device(mobile) {\n      margin: 0 px2rem(-16px);\n    }\n\n    // Arithmatex content\n    > * {\n      width: min-content;\n      // stylelint-disable-next-line declaration-no-important\n      margin: 1em auto !important;\n      padding: 0 px2rem(16px);\n      touch-action: auto;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Deletion, addition or comment\n  del.critic,\n  ins.critic,\n  .critic.comment {\n    box-decoration-break: clone;\n  }\n\n  // Deletion\n  del.critic {\n    background-color: var(--md-typeset-del-color);\n  }\n\n  // Addition\n  ins.critic {\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Comment\n  .critic.comment {\n    color: var(--md-code-hl-comment-color);\n\n    // Comment opening mark\n    &::before {\n      content: \"/* \";\n    }\n\n    // Comment closing mark\n    &::after {\n      content: \" */\";\n    }\n  }\n\n  // Critic block\n  .critic.block {\n    display: block;\n    margin: 1em 0;\n    padding-right: px2rem(16px);\n    padding-left: px2rem(16px);\n    overflow: auto;\n    box-shadow: none;\n\n    // Adjust spacing on first child\n    > :first-child {\n      margin-top: 0.5em;\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0.5em;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Details\n  details {\n    @extend .admonition;\n\n    display: flow-root;\n    padding-top: 0;\n    overflow: visible;\n\n    // Details title icon - rotate icon on transition to open state\n    &[open] > summary::after {\n      transform: rotate(90deg);\n    }\n\n    // Adjust spacing for details in closed state\n    &:not([open]) {\n      padding-bottom: 0;\n      box-shadow: none;\n\n      // Hack: we cannot set `overflow: hidden` on the `details` element (which\n      // is why we set it to `overflow: visible`, as the outline would not be\n      // visible when focusing. Therefore, we must set the border radius on the\n      // summary explicitly.\n      > summary {\n        border-radius: px2rem(2px);\n      }\n    }\n\n    // Hack: omit margin collapse\n    &::after {\n      display: table;\n      content: \"\";\n    }\n  }\n\n  // Details title\n  summary {\n    @extend .admonition-title;\n\n    display: block;\n    min-height: px2rem(20px);\n    padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);\n    border-top-left-radius: px2rem(2px);\n    border-top-right-radius: px2rem(2px);\n    cursor: pointer;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: px2rem(8px) px2rem(44px) px2rem(8px) px2rem(36px);\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Details marker\n    &::after {\n      position: absolute;\n      top: px2rem(8px);\n      right: px2rem(8px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: currentColor;\n      mask-image: var(--md-details-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      transform: rotate(0deg);\n      transition: transform 250ms;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(8px);\n        transform: rotate(180deg);\n      }\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Emoji and icon container\n  .emojione,\n  .twemoji,\n  .gemoji {\n    display: inline-flex;\n    height: px2em(18px);\n    vertical-align: text-top;\n\n    // Icon - inlined via mkdocs-material-extensions\n    svg {\n      width: px2em(18px);\n      max-height: 100%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: syntax highlighting\n// ----------------------------------------------------------------------------\n\n// Code block\n.highlight {\n  .o,   // Operator\n  .ow { // Operator, word\n    color: var(--md-code-hl-operator-color);\n  }\n\n  .p {  // Punctuation\n    color: var(--md-code-hl-punctuation-color);\n  }\n\n  .cpf, // Comment, preprocessor file\n  .l,   // Literal\n  .s,   // Literal, string\n  .sb,  // Literal, string backticks\n  .sc,  // Literal, string char\n  .s2,  // Literal, string double\n  .si,  // Literal, string interpol\n  .s1,  // Literal, string single\n  .ss { // Literal, string symbol\n    color: var(--md-code-hl-string-color);\n  }\n\n  .cp,  // Comment, pre-processor\n  .se,  // Literal, string escape\n  .sh,  // Literal, string heredoc\n  .sr,  // Literal, string regex\n  .sx { // Literal, string other\n    color: var(--md-code-hl-special-color);\n  }\n\n  .m,   // Number\n  .mb,  // Number, binary\n  .mf,  // Number, float\n  .mh,  // Number, hex\n  .mi,  // Number, integer\n  .il,  // Number, integer long\n  .mo { // Number, octal\n    color: var(--md-code-hl-number-color);\n  }\n\n  .k,   // Keyword,\n  .kd,  // Keyword, declaration\n  .kn,  // Keyword, namespace\n  .kp,  // Keyword, pseudo\n  .kr,  // Keyword, reserved\n  .kt { // Keyword, type\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .kc,  // Keyword, constant\n  .n {  // Name\n    color: var(--md-code-hl-name-color);\n  }\n\n  .no,  // Name, constant\n  .nb,  // Name, builtin\n  .bp { // Name, builtin pseudo\n    color: var(--md-code-hl-constant-color);\n  }\n\n  .nc,  // Name, class\n  .ne,  // Name, exception\n  .nf,  // Name, function\n  .nn { // Name, namespace\n    color: var(--md-code-hl-function-color);\n  }\n\n  .nd,  // Name, decorator\n  .ni,  // Name, entity\n  .nl,  // Name, label\n  .nt { // Name, tag\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .c,   // Comment\n  .cm,  // Comment, multiline\n  .c1,  // Comment, single\n  .ch,  // Comment, shebang\n  .cs,  // Comment, special\n  .sd { // Literal, string doc\n    color: var(--md-code-hl-comment-color);\n  }\n\n  .na,  // Name, attribute\n  .nv,  // Variable,\n  .vc,  // Variable, class\n  .vg,  // Variable, global\n  .vi { // Variable, instance\n    color: var(--md-code-hl-variable-color);\n  }\n\n  .ge,  // Generic, emph\n  .gr,  // Generic, error\n  .gh,  // Generic, heading\n  .go,  // Generic, output\n  .gp,  // Generic, prompt\n  .gs,  // Generic, strong\n  .gu,  // Generic, subheading\n  .gt { // Generic, traceback\n    color: var(--md-code-hl-generic-color);\n  }\n\n  .gd,  // Diff, delete\n  .gi { // Diff, insert\n    margin: 0 px2em(-2px);\n    padding: 0 px2em(2px);\n    border-radius: px2rem(2px);\n  }\n\n  .gd { // Diff, delete\n    background-color: var(--md-typeset-del-color);\n  }\n\n  .gi { // Diff, insert\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Highlighted line\n  .hll {\n    display: block;\n    margin: 0 px2em(-16px, 13.6px);\n    padding: 0 px2em(16px, 13.6px);\n    background-color: var(--md-code-hl-color);\n  }\n\n  // Code block line numbers (inline)\n  [data-linenos]::before {\n    position: sticky;\n    left: px2em(-16px, 13.6px);\n    float: left;\n    margin-right: px2em(16px, 13.6px);\n    margin-left: px2em(-16px, 13.6px);\n    padding-left: px2em(16px, 13.6px);\n    color: var(--md-default-fg-color--light);\n    background-color: var(--md-code-bg-color);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n    content: attr(data-linenos);\n    user-select: none;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Code block with line numbers\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n\n  // Set table elements to block layout, because otherwise the whole flexbox\n  // hacking won't work correctly\n  tbody,\n  td {\n    display: block;\n    padding: 0;\n  }\n\n  // We need to use flexbox layout, because otherwise it's not possible to\n  // make the code container scroll while keeping the line numbers static\n  tr {\n    display: flex;\n  }\n\n  // The pre tags are nested inside a table, so we need to omit the margin\n  // because it collapses below all the overflows\n  pre {\n    margin: 0;\n  }\n\n  // Code block line numbers - disable user selection, so code can be easily\n  // copied without accidentally also copying the line numbers\n  .linenos {\n    padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n    padding-right: 0;\n    font-size: px2em(13.6px);\n    background-color: var(--md-code-bg-color);\n    user-select: none;\n  }\n\n  // Code block line numbers container\n  .linenodiv {\n    padding-right: px2em(8px, 13.6px);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n\n    // Adjust colors and alignment\n    pre {\n      color: var(--md-default-fg-color--light);\n      text-align: right;\n    }\n  }\n\n  // Code block container - stretch to remaining space\n  .code {\n    flex: 1;\n    overflow: hidden;\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Code block with line numbers\n  .highlighttable {\n    margin: 1em 0;\n    direction: ltr;\n    border-radius: px2rem(2px);\n\n    // Omit rounded borders on contained code block\n    code {\n      border-radius: 0;\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Top-level code block\n    > .highlight {\n      margin: 1em px2rem(-16px);\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n\n      // Omit rounded borders\n      code {\n        border-radius: 0;\n      }\n    }\n\n    // Top-level code block with line numbers\n    > .highlighttable {\n      margin: 1em px2rem(-16px);\n      border-radius: 0;\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tabbed block content\n  .tabbed-content {\n    display: none;\n    order: 99;\n    width: 100%;\n    box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);\n\n    // [print]: Show all tabs (even hidden ones) when printing\n    @media print {\n      display: block;\n      order: initial;\n    }\n\n    // Code block is the only child of a tab - remove margin and mirror\n    // previous (now deprecated) SuperFences code block grouping behavior\n    > pre:only-child,\n    > .highlight:only-child pre,\n    > .highlighttable:only-child {\n      margin: 0;\n\n      // Omit rounded borders\n      > code {\n        border-top-left-radius: 0;\n        border-top-right-radius: 0;\n      }\n    }\n\n    // Adjust spacing for nested tab\n    > .tabbed-set {\n      margin: 0;\n    }\n  }\n\n  // Tabbed block container\n  .tabbed-set {\n    position: relative;\n    display: flex;\n    flex-wrap: wrap;\n    margin: 1em 0;\n    border-radius: px2rem(2px);\n\n    // Tab radio button - the Tabbed extension will generate radio buttons with\n    // labels, so tabs can be triggered without the necessity for JavaScript.\n    // This is pretty cool, as it has great accessibility out-of-the box, so\n    // we just hide the radio button and toggle the label color for indication.\n    > input {\n      position: absolute;\n      width: 0;\n      height: 0;\n      opacity: 0;\n\n      // Tab label for checked radio button\n      &:checked + label {\n        color: var(--md-accent-fg-color);\n        border-color: var(--md-accent-fg-color);\n\n        // Show tabbed block content\n        + .tabbed-content {\n          display: block;\n        }\n      }\n\n      // Tab label on focus\n      &:focus + label {\n        outline-style: auto;\n      }\n\n      // Hide outline for pointer devices\n      &:not(.focus-visible) + label {\n        outline: none;\n        -webkit-tap-highlight-color: transparent;\n      }\n    }\n\n    // Tab label\n    > label {\n      z-index: 1;\n      width: auto;\n      padding: px2em(12px, 12.8px) 1.25em px2em(10px, 12.8px);\n      color: var(--md-default-fg-color--light);\n      font-weight: 700;\n      font-size: px2rem(12.8px);\n      border-bottom: px2rem(2px) solid transparent;\n      cursor: pointer;\n      transition: color 250ms;\n\n      // Tab label on hover\n      &:hover {\n        color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tasklist item\n  .task-list-item {\n    position: relative;\n    list-style-type: none;\n\n    // Make checkbox items align with normal list items, but position\n    // everything in ems for correct layout at smaller font sizes\n    [type=\"checkbox\"] {\n      position: absolute;\n      top: 0.45em;\n      left: -2em;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: -2em;\n        left: initial;\n      }\n    }\n  }\n\n  // Hide native checkbox, when custom classes are enabled\n  .task-list-control [type=\"checkbox\"] {\n    z-index: -1;\n    opacity: 0;\n  }\n\n  // Tasklist indicator in unchecked state\n  .task-list-indicator::before {\n    position: absolute;\n    top: 0.15em;\n    left: px2em(-24px);\n    width: px2em(20px);\n    height: px2em(20px);\n    background-color: var(--md-default-fg-color--lightest);\n    mask-image: var(--md-tasklist-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: px2em(-24px);\n      left: initial;\n    }\n  }\n\n  // Tasklist indicator in checked state\n  [type=\"checkbox\"]:checked + .task-list-indicator::before {\n    background-color: $clr-green-a400;\n    mask-image: var(--md-tasklist-icon--checked);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // [tablet +]: Allow for rendering content as sidebars\n  @include break-from-device(tablet) {\n\n    // Modifier to float block elements\n    .inline {\n      float: left;\n      width: px2rem(234px);\n      margin-top: 0;\n      margin-right: px2rem(16px);\n      margin-bottom: px2rem(16px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n      }\n\n      // Modifier to move to end (ltr: right, rtl: left)\n      &.end {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          float: left;\n          margin-right: px2rem(16px);\n          margin-left: 0;\n        }\n      }\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/latest/assets/stylesheets/main.77f3fd56.min.css b/latest/assets/stylesheets/main.77f3fd56.min.css
deleted file mode 100644 (file)
index 8834c5c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-@charset "UTF-8";html{box-sizing:border-box;-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}*,:after,:before{box-sizing:inherit}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{display:block;box-sizing:initial;height:.05rem;padding:0;overflow:visible;border:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;font-size:inherit;background:transparent;border:0}input{border:0;outline:none}:root{--md-default-fg-color:rgba(0,0,0,0.87);--md-default-fg-color--light:rgba(0,0,0,0.54);--md-default-fg-color--lighter:rgba(0,0,0,0.32);--md-default-fg-color--lightest:rgba(0,0,0,0.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,0.7);--md-default-bg-color--lighter:hsla(0,0%,100%,0.3);--md-default-bg-color--lightest:hsla(0,0%,100%,0.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}:root>*{--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,0.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,0.5);--md-typeset-del-color:rgba(245,80,61,0.15);--md-typeset-ins-color:rgba(11,213,112,0.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,0.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,0.3);--md-footer-bg-color:rgba(0,0,0,0.87);--md-footer-bg-color--dark:rgba(0,0,0,0.32)}.md-icon svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family,_),SFMono-Regular,Consolas,Menlo,monospace}:root{--md-typeset-table--ascending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 4h2v12l5.5-5.5 1.42 1.42L12 19.84l-7.92-7.92L5.5 10.5 11 16V4z'/></svg>");--md-typeset-table--descending:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z'/></svg>")}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact;color-adjust:exact}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{display:flow-root;margin:1em 0}.md-typeset h1{margin:0 0 1.25em;color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:1.6em 0 .64em;font-size:1.5625em;line-height:1.4}.md-typeset h3{margin:1.6em 0 .8em;font-weight:400;font-size:1.25em;line-height:1.5;letter-spacing:-.01em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{margin:1em 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{margin:1.25em 0;color:var(--md-default-fg-color--light);font-weight:700;font-size:.8em;letter-spacing:-.01em}.md-typeset h5{text-transform:uppercase}.md-typeset hr{display:flow-root;margin:1.5em 0;border-bottom:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{padding:0 .2941176471em;font-size:.85em;word-break:break-word;background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset code:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:initial;padding:initial;background-color:initial;box-shadow:none}.md-typeset a>code{color:currentColor}.md-typeset pre{position:relative;line-height:1.4}.md-typeset pre>code{display:block;margin:0;padding:.7720588235em 1.1764705882em;overflow:auto;word-break:normal;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;touch-action:auto;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:44.9375em){.md-typeset>pre{margin:1em -.8rem}.md-typeset>pre code{border-radius:0}}.md-typeset kbd{display:inline-block;padding:0 .6666666667em;color:var(--md-default-fg-color);font-size:.75em;vertical-align:text-top;word-break:break-word;background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset}.md-typeset mark{color:inherit;word-break:break-word;background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{text-decoration:none;border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:focus:after,.md-typeset abbr[title]:hover:after{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:absolute;left:0;display:inline-block;width:auto;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;max-width:80%;margin-top:2em;padding:.2rem .3rem;color:var(--md-default-bg-color);font-size:.7rem;background-color:var(--md-default-fg-color);border-radius:.1rem;content:attr(title)}}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;color:var(--md-default-fg-color--light);border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid var(--md-default-fg-color--lighter);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1.5em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset img,.md-typeset svg{max-width:100%;height:auto}.md-typeset img[align=left],.md-typeset svg[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right],.md-typeset svg[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child,.md-typeset svg[align]:only-child{margin-top:0}.md-typeset figure{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;margin:0 auto;text-align:center}.md-typeset figure img{display:block}.md-typeset figcaption{max-width:24rem;margin:1em auto 2em;font-style:italic}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){display:inline-block;max-width:100%;overflow:auto;font-size:.64rem;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .05rem rgba(0,0,0,.1);touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.9375em 1.25em;color:var(--md-default-bg-color);vertical-align:top;background-color:var(--md-default-fg-color--light)}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{padding:.9375em 1.25em;vertical-align:top;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-typeset table:not([class]) tr{transition:background-color 125ms}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}.md-typeset table th[role=columnheader]:after{display:inline-block;width:1.2em;height:1.2em;margin-left:.5em;vertical-align:sub;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--ascending);mask-image:var(--md-typeset-table--ascending)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:currentColor;-webkit-mask-image:var(--md-typeset-table--descending);mask-image:var(--md-typeset-table--descending)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{height:100%;overflow-x:hidden;font-size:125%}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;font-size:.5rem;background-color:var(--md-default-bg-color)}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-state=lock]{position:fixed}}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.md-toggle{display:none}.md-skip{position:fixed;z-index:-1;margin:.5rem;padding:.3rem .5rem;color:var(--md-default-bg-color);font-size:.64rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(.4rem);opacity:0}.md-skip:focus{z-index:10;transform:translateY(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms}@page{margin:25mm}.md-announce{overflow:auto;background-color:var(--md-footer-bg-color)}@media print{.md-announce{display:none}}.md-announce__inner{margin:.6rem auto;padding:0 .8rem;color:var(--md-footer-fg-color);font-size:.7rem}:root{--md-clipboard-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1z'/></svg>")}.md-clipboard{position:absolute;top:.5em;right:.5em;z-index:1;width:1.5em;height:1.5em;color:var(--md-default-fg-color--lightest);border-radius:.1rem;cursor:pointer;transition:color .25s}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{display:block;width:1.125em;height:1.125em;margin:0 auto;background-color:currentColor;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-content{flex-grow:1;overflow:hidden;scroll-padding-top:51.2rem}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){.md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem;margin-left:.8rem}.md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-right:.8rem;margin-left:1.2rem}}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__button{float:right;margin:.4rem 0 .4rem .4rem;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left;margin-right:.4rem;margin-left:0}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}.md-dialog{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:fixed;right:.8rem;bottom:.8rem;left:auto;z-index:2;min-width:11.1rem;padding:.4rem .6rem;background-color:var(--md-default-fg-color);border-radius:.1rem;transform:translateY(100%);opacity:0;transition:transform 0ms .4s,opacity .4s;pointer-events:none}@media print{.md-dialog{display:none}}[dir=rtl] .md-dialog{right:auto;left:.8rem}.md-dialog[data-md-state=open]{transform:translateY(0);opacity:1;transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s;pointer-events:auto}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-typeset .md-button{display:inline-block;padding:.625em 2em;color:var(--md-primary-fg-color);font-weight:700;border:.1rem solid;border-radius:.1rem;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{color:var(--md-accent-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .md-input{height:1.8rem;padding:0 .6rem;font-size:.8rem;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 .025rem .05rem rgba(0,0,0,.1);transition:box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{box-shadow:0 .4rem 1rem rgba(0,0,0,.15),0 .025rem .05rem rgba(0,0,0,.15)}.md-typeset .md-input--stretch{width:100%}.md-header{position:-webkit-sticky;position:sticky;top:0;right:0;left:0;z-index:2;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent;transition:color .25s,background-color .25s}@media print{.md-header{display:none}}.md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),color .25s,background-color .25s,box-shadow .25s}.md-header[data-md-state=hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),color .25s,background-color .25s,box-shadow .25s}.md-header__inner{display:flex;align-items:center;padding:0 .2rem}.md-header__button{position:relative;z-index:1;display:inline-block;margin:.2rem;padding:.4rem;color:currentColor;vertical-align:middle;cursor:pointer;transition:opacity .25s}.md-header__button:focus,.md-header__button:hover{opacity:.7}.md-header__button:not(.focus-visible){outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{display:block;width:1.2rem;height:1.2rem;fill:currentColor}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{position:absolute;display:flex;max-width:100%;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-header__topic+.md-header__topic{z-index:-1;transform:translateX(1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__title{flex-grow:1;height:2.4rem;margin-right:.4rem;margin-left:1rem;font-size:.9rem;line-height:2.4rem}.md-header__title[data-md-state=active] .md-header__topic{z-index:-1;transform:translateX(-1.25rem);opacity:0;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;pointer-events:none}[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic{transform:translateX(1.25rem)}.md-header__title[data-md-state=active] .md-header__topic+.md-header__topic{z-index:0;transform:translateX(0);opacity:1;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:auto}.md-header__title>.md-header__ellipsis{position:relative;width:100%;height:100%}.md-header__options{display:flex;flex-shrink:0;max-width:100%;white-space:nowrap;transition:max-width 0ms .25s,opacity .25s .25s}.md-header__options>[data-md-state=hidden]{display:none}[data-md-toggle=search]:checked~.md-header .md-header__options{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){.md-header__source{display:block;width:11.7rem;max-width:11.7rem;margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem;margin-left:0}}@media screen and (min-width:76.25em){.md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-footer{color:var(--md-footer-fg-color);background-color:var(--md-footer-bg-color)}@media print{.md-footer{display:none}}.md-footer__inner{padding:.2rem;overflow:auto}.md-footer__link{display:flex;padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}@media screen and (min-width:45em){.md-footer__link{width:50%}}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}.md-footer__link--prev{float:left}@media screen and (max-width:44.9375em){.md-footer__link--prev{width:25%}.md-footer__link--prev .md-footer__title{display:none}}[dir=rtl] .md-footer__link--prev{float:right}[dir=rtl] .md-footer__link--prev svg{transform:scaleX(-1)}.md-footer__link--next{float:right;text-align:right}@media screen and (max-width:44.9375em){.md-footer__link--next{width:75%}}[dir=rtl] .md-footer__link--next{float:left;text-align:left}[dir=rtl] .md-footer__link--next svg{transform:scaleX(-1)}.md-footer__title{position:relative;flex-grow:1;max-width:calc(100% - 2.4rem);padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-footer-copyright{width:100%;margin:auto .6rem;padding:.4rem 0;color:var(--md-footer-fg-color--lighter);font-size:.64rem}@media screen and (min-width:45em){.md-footer-copyright{width:auto}}.md-footer-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-footer-social{padding:.6rem 0}}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-footer-social__link svg{max-height:.8rem;vertical-align:-25%;fill:currentColor}:root{--md-nav-icon--prev:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z'/></svg>");--md-nav-icon--next:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>");--md-toc-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 9h14V7H3v2m0 4h14v-2H3v2m0 4h14v-2H3v2m16 0h2v-2h-2v2m0-10v2h2V7h-2m0 6h2v-2h-2v2z'/></svg>")}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{width:auto;height:100%}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{display:block;width:2.4rem;height:2.4rem;fill:currentColor}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__link{display:block;margin-top:.625em;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:color 125ms;scroll-snap-align:start}.md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{display:block;width:100%;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);background-color:currentColor}.md-nav--primary .md-nav__link[for=__toc]~.md-nav,.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{position:absolute;top:0;right:0;left:0;z-index:1;display:flex;flex-direction:column;height:100%;background-color:var(--md-default-bg-color)}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;color:var(--md-default-fg-color--light);font-weight:400;line-height:2.4rem;white-space:nowrap;background-color:var(--md-default-fg-color--lightest);cursor:pointer}.md-nav--primary .md-nav__title .md-nav__icon{position:absolute;top:.4rem;left:.4rem;display:block;width:1.2rem;height:1.2rem;margin:.2rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem;left:auto}.md-nav--primary .md-nav__title .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav--primary .md-nav__title~.md-nav__list{overflow-y:auto;background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;-webkit-scroll-snap-type:y mandatory;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-nav--primary .md-nav__title .md-logo{position:absolute;top:.2rem;left:.2rem;display:block;margin:.2rem;padding:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-logo{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link .md-nav__icon{position:absolute;top:50%;right:.6rem;width:1.2rem;height:1.2rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{right:auto;left:.6rem}.md-nav--primary .md-nav__link .md-nav__icon:after{display:block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:initial}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;transform:translateX(100%);opacity:0;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{transform:translateX(0);opacity:1;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{display:block;padding:0 .2rem;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color--dark)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:block;padding-right:2.4rem;scroll-snap-align:none}[dir=rtl] .md-nav--integrated .md-nav__link[for=__toc]{padding-right:.8rem;padding-left:2.4rem}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav,.md-nav__toggle:indeterminate~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__title{display:block;padding:0;pointer-events:none;scroll-snap-align:start}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{float:right;width:.9rem;height:.9rem;transition:transform .25s}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:after{display:inline-block;width:100%;height:100%;vertical-align:-.1rem;background-color:currentColor;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav>.md-nav__title{display:block;padding:0 .6rem;pointer-events:none;scroll-snap-align:start}.md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav__item{padding-right:.6rem}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:block;margin-bottom:1.25em;border-left:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav>.md-nav__title{display:none}}:root{--md-search-result-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7c-.41-.25-.8-.56-1.14-.9-.33-.33-.61-.7-.86-1.1H6V4h7v5h5v1.18c.71.16 1.39.43 2 .82V8l-6-6m6.31 16.9c1.33-2.11.69-4.9-1.4-6.22-2.11-1.33-4.91-.68-6.22 1.4-1.34 2.11-.69 4.89 1.4 6.22 1.46.93 3.32.93 4.79.02L22 23.39 23.39 22l-3.08-3.1m-3.81.1a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5z'/></svg>")}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{z-index:1;opacity:0}@media screen and (max-width:59.9375em){.md-search__overlay{position:absolute;top:.2rem;left:-2.2rem;width:2rem;height:2rem;overflow:hidden;background-color:var(--md-default-bg-color);border-radius:1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;pointer-events:none}[dir=rtl] .md-search__overlay{right:-2.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:rgba(0,0,0,.54);cursor:pointer;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){.md-search__inner{position:fixed;top:0;left:100%;z-index:2;width:100%;height:100%;transform:translateX(5%);opacity:0;transition:right 0ms .3s,left 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;transform:translateX(0);opacity:1;transition:right 0ms 0ms,left 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;transform:translateX(-5%)}}@media screen and (min-width:60em){.md-search__inner{position:relative;float:right;width:11.7rem;padding:.1rem 0;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{position:relative}@media screen and (min-width:60em){.md-search__form{border-radius:.1rem}}.md-search__input{position:relative;z-index:2;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;transition:color .25s,background-color .25s,box-shadow .25s}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s;transition:color .25s}.md-search__input::-moz-placeholder{-moz-transition:color .25s;transition:color .25s}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}[data-md-toggle=search]:checked~.md-header .md-search__input{box-shadow:0 0 .6rem rgba(0,0,0,.07)}@media screen and (max-width:59.9375em){.md-search__input{width:100%;height:2.4rem;font-size:.9rem}}@media screen and (min-width:60em){.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;color:inherit;font-size:.8rem;background-color:rgba(0,0,0,.26);border-radius:.1rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}.md-search__input::-webkit-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-moz-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{color:var(--md-default-fg-color);text-overflow:clip;background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{position:absolute;z-index:2;width:1.2rem;height:1.2rem;cursor:pointer;transition:color .25s,opacity .25s}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){.md-search__icon[for=__search]{top:.6rem;left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem;left:auto}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}.md-search__icon[type=reset]{top:.3rem;right:.5rem;transform:scale(.75);opacity:0;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;pointer-events:none}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}@media screen and (max-width:59.9375em){.md-search__icon[type=reset]{top:.6rem;right:.8rem}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{transform:scale(1);opacity:1;pointer-events:auto}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;z-index:1;width:100%;overflow:hidden;border-radius:0 0 .1rem .1rem}@media screen and (max-width:59.9375em){.md-search__output{top:2.4rem;bottom:0}}@media screen and (min-width:60em){.md-search__output{top:1.9rem;opacity:0;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}}.md-search__scrollwrap{height:100%;overflow-y:auto;background-color:var(--md-default-bg-color);-webkit-backface-visibility:hidden;backface-visibility:hidden;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1), (max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{padding:0 .8rem;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;background-color:var(--md-default-fg-color--lightest);scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}}.md-search-result__list{margin:0;padding:0;list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;transition:background-color .25s;scroll-snap-align:start}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{display:block;padding:.75em .8rem;color:var(--md-typeset-a-color);font-size:.64rem;outline:0;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}@media screen and (min-width:60em){.md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__more summary:focus,.md-search-result__more summary:hover{color:var(--md-accent-fg-color);background-color:var(--md-accent-fg-color--transparent)}.md-search-result__more summary::-webkit-details-marker,.md-search-result__more summary::marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{position:relative;padding:0 .8rem;overflow:hidden}@media screen and (min-width:60em){.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-weight:400;font-size:.8rem;line-height:1.4}.md-search-result__icon{position:absolute;left:0;width:1.2rem;height:1.2rem;margin:.5rem;color:var(--md-default-fg-color--light)}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{display:inline-block;width:100%;height:100%;background-color:currentColor;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-search-result__icon{right:0;left:auto}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{margin:.5em 0;font-weight:700;font-size:.64rem;line-height:1.6}.md-search-result__teaser{display:-webkit-box;max-height:2rem;margin:.5em 0;overflow:hidden;color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}@media screen and (max-width:44.9375em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{max-height:3rem;-webkit-line-clamp:3}}.md-search-result__teaser mark{text-decoration:underline;background-color:initial}.md-search-result__terms{margin:.5em 0;font-size:.64rem;font-style:italic}.md-search-result mark{color:var(--md-accent-fg-color);background-color:initial}.md-select{position:relative;z-index:1}.md-select__inner{position:absolute;top:calc(100% - .2rem);left:50%;max-height:0;margin-top:.2rem;color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);transform:translate3d(-50%,.3rem,0);opacity:0;transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;transform:translate3d(-50%,0,0);opacity:1;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s}.md-select__inner:after{position:absolute;top:0;left:50%;width:0;height:0;margin-top:-.2rem;margin-left:-.2rem;border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);content:""}.md-select__list{max-height:inherit;margin:0;padding:0;overflow:auto;font-size:.8rem;list-style-type:none;border-radius:.1rem}.md-select__item{line-height:1.8rem}.md-select__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;cursor:pointer;transition:background-color .25s,color .25s;scroll-snap-align:start}[dir=rtl] .md-select__link{padding-right:.6rem;padding-left:1.2rem}.md-select__link:focus,.md-select__link:hover{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{position:-webkit-sticky;position:sticky;top:2.4rem;flex-shrink:0;align-self:flex-start;width:12.1rem;padding:1.2rem 0}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;z-index:3;display:block;width:12.1rem;height:100%;background-color:var(--md-default-bg-color);transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;-webkit-scroll-snap-type:none;-ms-scroll-snap-type:none;scroll-snap-type:none;overflow:hidden}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:var(--md-default-fg-color--lighter) transparent}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{position:fixed;top:0;z-index:3;width:0;height:0;background-color:rgba(0,0,0,.54);opacity:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s}[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s}}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{transform:translateY(100%);opacity:0}50%{opacity:0}to{transform:translateY(0);opacity:1}}.md-source{display:block;font-size:.65rem;line-height:1.2;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity .25s}.md-source:focus,.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;width:2.4rem;height:2.4rem;vertical-align:middle}.md-source__icon svg{margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);margin-left:.6rem;overflow:hidden;font-weight:700;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{margin:0;padding:0;overflow:hidden;font-weight:700;font-size:.55rem;list-style-type:none;opacity:.75}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"·"}.md-source__fact:first-child:before{display:none}.md-tabs{width:100%;overflow:auto;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color);transition:background-color .25s}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs__list{margin:0 0 0 .2rem;padding:0;white-space:nowrap;list-style:none;contain:content}[dir=rtl] .md-tabs__list{margin-right:.2rem;margin-left:0}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;font-size:.7rem;-webkit-backface-visibility:hidden;backface-visibility:hidden;opacity:.7;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden] .md-tabs__link{transform:translateY(50%);opacity:0;transition:transform 0ms .1s,opacity .1s}:root{--md-version-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'><path d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/></svg>")}.md-version{flex-shrink:0;height:2.4rem;font-size:.8rem}.md-version__current{position:relative;top:.05rem;margin-right:.4rem;margin-left:1.4rem}[dir=rtl] .md-version__current{margin-right:1.4rem;margin-left:.4rem}.md-version__current:after{display:inline-block;width:.4rem;height:.6rem;margin-left:.4rem;background-color:currentColor;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;content:""}[dir=rtl] .md-version__current:after{margin-right:.4rem;margin-left:0}.md-version__list{position:absolute;top:.15rem;z-index:1;max-height:1.8rem;margin:.2rem .8rem;padding:0;overflow:auto;color:var(--md-default-fg-color);list-style-type:none;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.25);opacity:0;transition:max-height 0ms .5s,opacity .25s .25s;-webkit-scroll-snap-type:y mandatory;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory}.md-version__list:focus-within,.md-version__list:hover{max-height:10rem;opacity:1;transition:max-height .25s,opacity .25s}.md-version__item{line-height:1.8rem}.md-version__link{display:block;width:100%;padding-right:1.2rem;padding-left:.6rem;white-space:nowrap;cursor:pointer;transition:color .25s,background-color .25s;scroll-snap-align:start}[dir=rtl] .md-version__link{padding-right:.6rem;padding-left:1.2rem}.md-version__link:focus,.md-version__link:hover{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");--md-admonition-icon--abstract:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z'/></svg>");--md-admonition-icon--info:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");--md-admonition-icon--tip:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");--md-admonition-icon--success:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/></svg>");--md-admonition-icon--question:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M15.07 11.25l-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");--md-admonition-icon--warning:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");--md-admonition-icon--failure:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z'/></svg>");--md-admonition-icon--danger:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z'/></svg>");--md-admonition-icon--bug:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");--md-admonition-icon--example:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");--md-admonition-icon--quote:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>")}.md-typeset .admonition,.md-typeset details{margin:1.5625em 0;padding:0 .6rem;overflow:hidden;color:var(--md-admonition-fg-color);font-size:.64rem;page-break-inside:avoid;background-color:var(--md-admonition-bg-color);border-left:.2rem solid #448aff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 .025rem .05rem rgba(0,0,0,.05)}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition-title,.md-typeset summary{position:relative;margin:0 -.6rem 0 -.8rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(68,138,255,.1);border-left:.2rem solid #448aff}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{margin:0 -.8rem 0 -.6rem;padding:.4rem 2rem .4rem .6rem;border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}.md-typeset .admonition-title:before,.md-typeset summary:before{position:absolute;left:.6rem;width:1rem;height:1rem;background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem;left:auto}.md-typeset .admonition-title code,.md-typeset summary code{margin:initial;padding:initial;color:currentColor;background-color:initial;border-radius:initial;box-shadow:none}.md-typeset .admonition-title+.tabbed-set:last-child,.md-typeset summary+.tabbed-set:last-child{margin-top:0}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:rgba(68,138,255,.1);border-color:#448aff}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-color:#00b0ff}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary,.md-typeset .summary>.admonition-title,.md-typeset .summary>summary,.md-typeset .tldr>.admonition-title,.md-typeset .tldr>summary{background-color:rgba(0,176,255,.1);border-color:#00b0ff}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before,.md-typeset .summary>.admonition-title:before,.md-typeset .summary>summary:before,.md-typeset .tldr>.admonition-title:before,.md-typeset .tldr>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-color:#00b8d4}.md-typeset .info>.admonition-title,.md-typeset .info>summary,.md-typeset .todo>.admonition-title,.md-typeset .todo>summary{background-color:rgba(0,184,212,.1);border-color:#00b8d4}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before,.md-typeset .todo>.admonition-title:before,.md-typeset .todo>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .hint>.admonition-title,.md-typeset .hint>summary,.md-typeset .important>.admonition-title,.md-typeset .important>summary,.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:rgba(0,191,165,.1);border-color:#00bfa5}.md-typeset .hint>.admonition-title:before,.md-typeset .hint>summary:before,.md-typeset .important>.admonition-title:before,.md-typeset .important>summary:before,.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-color:#00c853}.md-typeset .check>.admonition-title,.md-typeset .check>summary,.md-typeset .done>.admonition-title,.md-typeset .done>summary,.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:rgba(0,200,83,.1);border-color:#00c853}.md-typeset .check>.admonition-title:before,.md-typeset .check>summary:before,.md-typeset .done>.admonition-title:before,.md-typeset .done>summary:before,.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-color:#64dd17}.md-typeset .faq>.admonition-title,.md-typeset .faq>summary,.md-typeset .help>.admonition-title,.md-typeset .help>summary,.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:rgba(100,221,23,.1);border-color:#64dd17}.md-typeset .faq>.admonition-title:before,.md-typeset .faq>summary:before,.md-typeset .help>.admonition-title:before,.md-typeset .help>summary:before,.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-color:#ff9100}.md-typeset .attention>.admonition-title,.md-typeset .attention>summary,.md-typeset .caution>.admonition-title,.md-typeset .caution>summary,.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:rgba(255,145,0,.1);border-color:#ff9100}.md-typeset .attention>.admonition-title:before,.md-typeset .attention>summary:before,.md-typeset .caution>.admonition-title:before,.md-typeset .caution>summary:before,.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-color:#ff5252}.md-typeset .fail>.admonition-title,.md-typeset .fail>summary,.md-typeset .failure>.admonition-title,.md-typeset .failure>summary,.md-typeset .missing>.admonition-title,.md-typeset .missing>summary{background-color:rgba(255,82,82,.1);border-color:#ff5252}.md-typeset .fail>.admonition-title:before,.md-typeset .fail>summary:before,.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before,.md-typeset .missing>.admonition-title:before,.md-typeset .missing>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-color:#ff1744}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary,.md-typeset .error>.admonition-title,.md-typeset .error>summary{background-color:rgba(255,23,68,.1);border-color:#ff1744}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before,.md-typeset .error>.admonition-title:before,.md-typeset .error>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:rgba(245,0,87,.1);border-color:#f50057}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:rgba(124,77,255,.1);border-color:#7c4dff}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .cite>.admonition-title,.md-typeset .cite>summary,.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:hsla(0,0%,62%,.1);border-color:#9e9e9e}.md-typeset .cite>.admonition-title:before,.md-typeset .cite>summary:before,.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z'/></svg>")}.md-typeset [id^="fnref:"]:target{scroll-margin-top:0;margin-top:-3.4rem;padding-top:3.4rem}.md-typeset [id^="fn:"]:target{scroll-margin-top:0;margin-top:-3.45rem;padding-top:3.45rem}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{transition:color 125ms}.md-typeset .footnote li:target{color:var(--md-default-fg-color)}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{transform:translateX(0);opacity:1}.md-typeset .footnote li>:first-child{margin-top:0}.md-typeset .footnote-backref{display:inline-block;color:var(--md-typeset-a-color);font-size:0;vertical-align:text-bottom;transform:translateX(.25rem);opacity:0;transition:color .25s,transform .25s .25s,opacity 125ms .25s}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);transform:translateX(0);opacity:1}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{display:inline-block;width:.8rem;height:.8rem;background-color:currentColor;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;color:var(--md-default-fg-color--lighter);opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{scroll-margin-top:3.6rem}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{scroll-margin-top:0}.md-typeset h1:target:before,.md-typeset h2:target:before,.md-typeset h3:target:before{display:block;margin-top:-3.4rem;padding-top:3.4rem;content:""}.md-typeset h4:target{scroll-margin-top:0}.md-typeset h4:target:before{display:block;margin-top:-3.45rem;padding-top:3.45rem;content:""}.md-typeset h5:target,.md-typeset h6:target{scroll-margin-top:0}.md-typeset h5:target:before,.md-typeset h6:target:before{display:block;margin-top:-3.6rem;padding-top:3.6rem;content:""}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{width:-webkit-min-content;width:-moz-min-content;width:min-content;margin:1em auto!important;padding:0 .8rem;touch-action:auto}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;overflow:auto;box-shadow:none}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z'/></svg>")}.md-typeset details{display:flow-root;padding-top:0;overflow:visible}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){padding-bottom:0;box-shadow:none}.md-typeset details:not([open])>summary{border-radius:.1rem}.md-typeset details:after{display:table;content:""}.md-typeset summary{display:block;min-height:1rem;padding:.4rem 1.8rem .4rem 2rem;border-top-left-radius:.1rem;border-top-right-radius:.1rem;cursor:pointer}[dir=rtl] .md-typeset summary{padding:.4rem 2.2rem .4rem 1.8rem}.md-typeset summary:not(.focus-visible){outline:none;-webkit-tap-highlight-color:transparent}.md-typeset summary:after{position:absolute;top:.4rem;right:.4rem;width:1rem;height:1rem;background-color:currentColor;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transform:rotate(0deg);transition:transform .25s;content:""}[dir=rtl] .md-typeset summary:after{right:auto;left:.4rem;transform:rotate(180deg)}.md-typeset summary::-webkit-details-marker,.md-typeset summary::marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{width:1.125em;max-height:100%;fill:currentColor}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{margin:0 -.125em;padding:0 .125em;border-radius:.1rem}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em;background-color:var(--md-code-hl-color)}.highlight [data-linenos]:before{position:-webkit-sticky;position:sticky;left:-1.1764705882em;float:left;margin-right:1.1764705882em;margin-left:-1.1764705882em;padding-left:1.1764705882em;color:var(--md-default-fg-color--light);background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable{display:flow-root;overflow:hidden}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable .linenos{padding:.7720588235em 0 .7720588235em 1.1764705882em;font-size:.85em;background-color:var(--md-code-bg-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{padding-right:.5882352941em;box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;overflow:hidden}.md-typeset .highlighttable{margin:1em 0;direction:ltr;border-radius:.1rem}.md-typeset .highlighttable code{border-radius:0}@media screen and (max-width:44.9375em){.md-typeset>.highlight{margin:1em -.8rem}.md-typeset>.highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.highlight code{border-radius:0}.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.highlighttable .hll{margin:0 -.8rem;padding:0 .8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{position:relative;margin:0;color:inherit;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.md-typeset .keys span{padding:0 .2em;color:var(--md-default-fg-color--light)}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{padding-right:.4em;content:"⎇"}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{padding-right:.4em;content:"⌘"}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{padding-right:.4em;content:"⌃"}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{padding-right:.4em;content:"◆"}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{padding-right:.4em;content:"⌥"}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{padding-right:.4em;content:"⇧"}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{padding-right:.4em;content:"❖"}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{padding-right:.4em;content:"⊞"}.md-typeset .keys .key-arrow-down:before{padding-right:.4em;content:"↓"}.md-typeset .keys .key-arrow-left:before{padding-right:.4em;content:"←"}.md-typeset .keys .key-arrow-right:before{padding-right:.4em;content:"→"}.md-typeset .keys .key-arrow-up:before{padding-right:.4em;content:"↑"}.md-typeset .keys .key-backspace:before{padding-right:.4em;content:"⌫"}.md-typeset .keys .key-backtab:before{padding-right:.4em;content:"⇤"}.md-typeset .keys .key-caps-lock:before{padding-right:.4em;content:"⇪"}.md-typeset .keys .key-clear:before{padding-right:.4em;content:"⌧"}.md-typeset .keys .key-context-menu:before{padding-right:.4em;content:"☰"}.md-typeset .keys .key-delete:before{padding-right:.4em;content:"⌦"}.md-typeset .keys .key-eject:before{padding-right:.4em;content:"⏏"}.md-typeset .keys .key-end:before{padding-right:.4em;content:"⤓"}.md-typeset .keys .key-escape:before{padding-right:.4em;content:"⎋"}.md-typeset .keys .key-home:before{padding-right:.4em;content:"⤒"}.md-typeset .keys .key-insert:before{padding-right:.4em;content:"⎀"}.md-typeset .keys .key-page-down:before{padding-right:.4em;content:"⇟"}.md-typeset .keys .key-page-up:before{padding-right:.4em;content:"⇞"}.md-typeset .keys .key-print-screen:before{padding-right:.4em;content:"⎙"}.md-typeset .keys .key-tab:after{padding-left:.4em;content:"⇥"}.md-typeset .keys .key-num-enter:after{padding-left:.4em;content:"⌤"}.md-typeset .keys .key-enter:after{padding-left:.4em;content:"⏎"}.md-typeset .tabbed-content{display:none;order:99;width:100%;box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}@media print{.md-typeset .tabbed-content{display:block;order:0}}.md-typeset .tabbed-content>.highlight:only-child pre,.md-typeset .tabbed-content>.highlighttable:only-child,.md-typeset .tabbed-content>pre:only-child{margin:0}.md-typeset .tabbed-content>.highlight:only-child pre>code,.md-typeset .tabbed-content>.highlighttable:only-child>code,.md-typeset .tabbed-content>pre:only-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-content>.tabbed-set{margin:0}.md-typeset .tabbed-set{position:relative;display:flex;flex-wrap:wrap;margin:1em 0;border-radius:.1rem}.md-typeset .tabbed-set>input{position:absolute;width:0;height:0;opacity:0}.md-typeset .tabbed-set>input:checked+label{color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:checked+label+.tabbed-content{display:block}.md-typeset .tabbed-set>input:focus+label{outline-style:auto}.md-typeset .tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.md-typeset .tabbed-set>label{z-index:1;width:auto;padding:.9375em 1.25em .78125em;color:var(--md-default-fg-color--light);font-weight:700;font-size:.64rem;border-bottom:.1rem solid transparent;cursor:pointer;transition:color .25s}.md-typeset .tabbed-set>label:hover{color:var(--md-accent-fg-color)}:root{--md-tasklist-icon:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>");--md-tasklist-icon--checked:url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12zm16.28-2.72a.75.75 0 0 0-1.06-1.06l-5.97 5.97-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l6.5-6.5z'/></svg>")}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control [type=checkbox]{z-index:-1;opacity:0}.md-typeset .task-list-indicator:before{position:absolute;top:.15em;left:-1.5em;width:1.25em;height:1.25em;background-color:var(--md-default-fg-color--lightest);-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em;left:auto}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media screen and (min-width:45em){.md-typeset .inline{float:left;width:11.7rem;margin-top:0;margin-right:.8rem;margin-bottom:.8rem}.md-typeset .inline.end,[dir=rtl] .md-typeset .inline{float:right;margin-right:0;margin-left:.8rem}[dir=rtl] .md-typeset .inline.end{float:left;margin-right:.8rem;margin-left:0}}
-/*# sourceMappingURL=main.77f3fd56.min.css.map */
\ No newline at end of file
diff --git a/latest/assets/stylesheets/main.77f3fd56.min.css.map b/latest/assets/stylesheets/main.77f3fd56.min.css.map
deleted file mode 100644 (file)
index 20accb2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_reset.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","node_modules/material-shadows/material-shadows.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_announce.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"AAsJI,gBCgrEJ,CC1yEA,KACE,qBAAA,CACA,6BAAA,CAAA,0BAAA,CAAA,yBAAA,CAAA,qBD1BF,CC8BA,iBAGE,kBD3BF,CC+BA,KACE,QD5BF,CCgCA,qBAIE,uCD7BF,CCiCA,EACE,aAAA,CACA,oBD9BF,CCkCA,GACE,aAAA,CACA,kBAAA,CACA,aAAA,CACA,SAAA,CACA,gBAAA,CACA,QD/BF,CCmCA,MACE,aDhCF,CCoCA,QAEE,eDjCF,CCqCA,IACE,iBDlCF,CCsCA,MACE,uBAAA,CACA,gBDnCF,CCuCA,MAEE,eAAA,CACA,kBDpCF,CCwCA,OACE,QAAA,CACA,SAAA,CACA,iBAAA,CACA,sBAAA,CACA,QDrCF,CCyCA,MACE,QAAA,CACA,YDtCF,CE7CA,MAGE,sCAAA,CACA,6CAAA,CACA,+CAAA,CACA,gDAAA,CACA,0BAAA,CACA,gDAAA,CACA,kDAAA,CACA,oDAAA,CAGA,6BAAA,CACA,oCAAA,CACA,mCAAA,CACA,0BAAA,CACA,gDAAA,CAGA,4BAAA,CACA,sDAAA,CACA,yBAAA,CACA,+CF0CF,CEvCE,QAGE,0BAAA,CACA,0BAAA,CAGA,sCAAA,CACA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CACA,+CAAA,CAGA,2CAAA,CAGA,2CAAA,CACA,4CAAA,CAGA,8BAAA,CACA,kCAAA,CACA,qCAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,yBAAA,CACA,+CAAA,CACA,iDAAA,CACA,qCAAA,CACA,2CFyBJ,CG9FE,aACE,aAAA,CACA,YAAA,CACA,aAAA,CACA,iBHiGJ,CIxGA,KACE,kCAAA,CACA,iCJ2GF,CIvGA,WAGE,mCAAA,CACA,oGJ0GF,CIpGA,wBARE,6BJoHF,CI5GA,aAIE,4BAAA,CACA,gFJuGF,CI7FA,MACE,sNAAA,CACA,wNJgGF,CIzFA,YACE,eAAA,CACA,eAAA,CACA,gCAAA,CAAA,kBJ4FF,CIxFE,aAPF,YAQI,gBJ2FF,CACF,CIxFE,uGAME,iBAAA,CACA,YJ0FJ,CItFE,eACE,iBAAA,CACA,uCAAA,CAEA,aAAA,CACA,eJyFJ,CIpFE,8BAPE,eAAA,CAGA,qBJ+FJ,CI3FE,eACE,oBAAA,CAEA,kBAAA,CACA,eJuFJ,CIlFE,eACE,mBAAA,CACA,eAAA,CACA,gBAAA,CACA,eAAA,CACA,qBJoFJ,CIhFE,kBACE,eJkFJ,CI9EE,eACE,YAAA,CACA,eAAA,CACA,qBJgFJ,CI5EE,8BAEE,eAAA,CACA,uCAAA,CACA,eAAA,CACA,cAAA,CACA,qBJ8EJ,CI1EE,eACE,wBJ4EJ,CIxEE,eACE,iBAAA,CACA,cAAA,CACA,+DJ0EJ,CItEE,cACE,+BAAA,CACA,qBJwEJ,CIrEI,mCAEE,sBJsEN,CIlEI,wCAEE,+BJmEN,CI9DE,iDAGE,6BAAA,CACA,aJgEJ,CI7DI,aAPF,iDAQI,oBJkEJ,CACF,CI9DE,iBACE,uBAAA,CACA,eAAA,CACA,qBAAA,CACA,wCAAA,CACA,mBAAA,CACA,kCAAA,CAAA,0BJgEJ,CI7DI,qCACE,YAAA,CACA,uCJ+DN,CI1DE,wHAME,cAAA,CACA,eAAA,CACA,wBAAA,CACA,eJ4DJ,CIxDE,mBACE,kBJ0DJ,CItDE,gBACE,iBAAA,CACA,eJwDJ,CIrDI,qBACE,aAAA,CACA,QAAA,CACA,oCAAA,CACA,aAAA,CACA,iBAAA,CACA,eAAA,CACA,kCAAA,CAAA,0BAAA,CACA,iBAAA,CACA,oBAAA,CACA,+DJuDN,CIpDM,2BACE,qDJsDR,CIlDM,wCACE,WAAA,CACA,YJoDR,CIhDM,8CACE,oDJkDR,CI/CQ,oDACE,0CJiDV,CK5FI,wCDqDA,gBACE,iBJ0CJ,CIvCI,qBACE,eJyCN,CACF,CIpCE,gBACE,oBAAA,CACA,uBAAA,CACA,gCAAA,CACA,eAAA,CACA,uBAAA,CACA,qBAAA,CACA,4CAAA,CACA,mBAAA,CACA,mKJsCJ,CI/BE,iBACE,aAAA,CACA,qBAAA,CACA,6CAAA,CACA,kCAAA,CAAA,0BJiCJ,CI7BE,iBACE,oBAAA,CACA,6DAAA,CACA,WJ+BJ,CI5BI,oBANF,iBAOI,iBJ+BJ,CI5BI,wEEzRJ,gGAAA,CF6RM,iBAAA,CACA,MAAA,CACA,oBAAA,CACA,UAAA,CACA,6BAAA,CAAA,0BAAA,CAAA,qBAAA,CACA,aAAA,CACA,cAAA,CACA,mBAAA,CACA,gCAAA,CACA,eAAA,CACA,2CAAA,CACA,mBAAA,CACA,mBJ4BN,CACF,CIvBE,kBACE,WJyBJ,CIrBE,gCAEE,qBJuBJ,CIpBI,oDACE,sBAAA,CACA,aJuBN,CIlBE,uBACE,kBAAA,CACA,uCAAA,CACA,2DJoBJ,CIjBI,iCACE,mBAAA,CACA,cAAA,CACA,4DAAA,CACA,mBJmBN,CIdE,eACE,oBJgBJ,CIZE,8BAEE,kBAAA,CACA,SJcJ,CIXI,kDACE,mBAAA,CACA,aJcN,CIVI,oCACE,2BJaN,CIVM,0CACE,2BJaR,CIRI,oCACE,kBAAA,CACA,kBJWN,CIRM,wDACE,mBAAA,CACA,aJWR,CIPM,kGAEE,aJWR,CIPM,0DACE,eJUR,CINM,oFAEE,yBJUR,CIPQ,4HACE,mBAAA,CACA,aJYV,CILE,eACE,0BJOJ,CIJI,yBACE,oBAAA,CACA,aJMN,CIDE,gCAEE,cAAA,CACA,WJGJ,CIAI,wDAEE,oBJGN,CICI,0DAEE,oBJEN,CIEI,oEACE,YJCN,CIIE,mBACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CACA,cAAA,CACA,aAAA,CACA,iBJFJ,CIKI,uBACE,aJHN,CIQE,uBACE,eAAA,CACA,mBAAA,CACA,iBJNJ,CIUE,mBACE,cJRJ,CIYE,+BACE,oBAAA,CACA,cAAA,CACA,aAAA,CACA,gBAAA,CACA,2CAAA,CACA,mBAAA,CACA,kEACE,CAEF,iBJZJ,CIeI,aAbF,+BAcI,aJZJ,CACF,CIiBI,iCACE,gBJfN,CIuBM,8FACE,YJpBR,CIwBM,4FACE,eJrBR,CI0BI,8FAEE,eJxBN,CI2BM,kHACE,gBJxBR,CI6BI,kCACE,cAAA,CACA,sBAAA,CACA,gCAAA,CACA,kBAAA,CACA,kDJ3BN,CI8BM,oCACE,aJ5BR,CIiCI,kCACE,sBAAA,CACA,kBAAA,CACA,4DJ/BN,CImCI,kCACE,iCJjCN,CIoCM,wCACE,iCAAA,CACA,sDJlCR,CIsCM,iDACE,YJpCR,CIyCI,iCACE,iBJvCN,CI4CE,wCACE,cJ1CJ,CI6CI,8CACE,oBAAA,CACA,WAAA,CACA,YAAA,CACA,gBAAA,CACA,kBAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UJ3CN,CI+CI,mEACE,6BAAA,CACA,qDAAA,CAAA,6CJ7CN,CIiDI,oEACE,6BAAA,CACA,sDAAA,CAAA,8CJ/CN,CIoDE,wBACE,iBAAA,CACA,eAAA,CACA,iBJlDJ,CIsDE,mBACE,oBAAA,CACA,kBAAA,CACA,eJpDJ,CIuDI,aANF,mBAOI,aJpDJ,CACF,CIuDI,8BACE,aAAA,CACA,UAAA,CACA,QAAA,CACA,eJrDN,COpiBA,KACE,WAAA,CACA,iBAAA,CAOA,cPiiBF,CKxYI,oCElKJ,KAaI,gBPiiBF,CACF,CK7YI,oCElKJ,KAkBI,cPiiBF,CACF,CO5hBA,KACE,iBAAA,CACA,YAAA,CACA,qBAAA,CACA,UAAA,CACA,eAAA,CAGA,eAAA,CACA,2CP6hBF,CO1hBE,aAZF,KAaI,aP6hBF,CACF,CK9YI,wCE5IF,yBAII,cP0hBJ,CACF,COjhBA,SACE,eAAA,CACA,iBAAA,CACA,gBPohBF,COhhBA,cACE,YAAA,CACA,qBAAA,CACA,WPmhBF,COhhBE,aANF,cAOI,aPmhBF,CACF,CO/gBA,SACE,WPkhBF,CO/gBE,gBACE,YAAA,CACA,WAAA,CACA,iBPihBJ,CO5gBA,aACE,eAAA,CACA,kBAAA,CACA,sBP+gBF,COtgBA,WACE,YPygBF,COrgBA,SACE,cAAA,CAGA,UAAA,CACA,YAAA,CACA,mBAAA,CACA,gCAAA,CACA,gBAAA,CACA,2CAAA,CACA,mBAAA,CACA,2BAAA,CACA,SPsgBF,COngBE,eACE,UAAA,CACA,uBAAA,CACA,SAAA,CACA,oEPqgBJ,CO1fA,MACE,WP6fF,CQnoBA,aACE,aAAA,CACA,0CRqoBF,CQloBE,aALF,aAMI,YRqoBF,CACF,CQloBE,oBACE,iBAAA,CACA,eAAA,CACA,+BAAA,CACA,eRooBJ,CSlpBA,MACE,+PTqpBF,CS/oBA,cACE,iBAAA,CACA,QAAA,CACA,UAAA,CACA,SAAA,CACA,WAAA,CACA,YAAA,CACA,0CAAA,CACA,mBAAA,CACA,cAAA,CACA,qBTkpBF,CS/oBE,aAbF,cAcI,YTkpBF,CACF,CS/oBE,kCACE,YAAA,CACA,uCTipBJ,CS7oBE,qBACE,uCT+oBJ,CS3oBE,wCAEE,+BT4oBJ,CSvoBE,oBACE,aAAA,CACA,aAAA,CACA,cAAA,CACA,aAAA,CACA,6BAAA,CACA,2CAAA,CAAA,mCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UTyoBJ,CSroBE,sBACE,cTuoBJ,CSpoBI,2BACE,2CTsoBN,CShoBI,kEAEE,+BAAA,CACA,uDTioBN,CUvsBA,YACE,WAAA,CAMA,eAAA,CACA,0BVqsBF,CUlsBE,mBACE,qBAAA,CACA,iBVosBJ,CK/iBI,sCK/IE,kEACE,kBVisBN,CU9rBM,4EACE,mBAAA,CACA,iBVgsBR,CU3rBI,oEACE,mBV6rBN,CU1rBM,8EACE,kBAAA,CACA,kBV4rBR,CACF,CUtrBI,0BACE,aAAA,CACA,YAAA,CACA,UVwrBN,CUprBI,+BACE,eVsrBN,CUhrBE,oBACE,WAAA,CAEA,0BAAA,CACA,SVkrBJ,CU/qBI,aAPF,oBAQI,YVkrBJ,CACF,CU/qBI,8BACE,UAAA,CACA,kBAAA,CACA,aVirBN,CU9qBM,kCACE,oBVgrBR,CU3qBI,gCACE,yCV6qBN,CUzqBI,wBACE,cAAA,CACA,kBV2qBN,CWnwBA,WLFE,gGAAA,CKKA,cAAA,CACA,WAAA,CACA,YAAA,CACA,SAAA,CACA,SAAA,CACA,iBAAA,CACA,mBAAA,CACA,2CAAA,CACA,mBAAA,CACA,0BAAA,CACA,SAAA,CACA,wCACE,CAEF,mBXmwBF,CWhwBE,aApBF,WAqBI,YXmwBF,CACF,CWhwBE,qBACE,UAAA,CACA,UXkwBJ,CW9vBE,+BACE,uBAAA,CACA,SAAA,CACA,kEACE,CAEF,mBX8vBJ,CW1vBE,kBACE,gCAAA,CACA,eX4vBJ,CYpyBE,uBACE,oBAAA,CACA,kBAAA,CACA,gCAAA,CACA,eAAA,CACA,kBAAA,CACA,mBAAA,CACA,gEZuyBJ,CYjyBI,gCACE,gCAAA,CACA,2CAAA,CACA,uCZmyBN,CY/xBI,0DAEE,+BAAA,CACA,0CAAA,CACA,sCZgyBN,CY3xBE,sBACE,aAAA,CACA,eAAA,CACA,eAAA,CACA,mBAAA,CACA,uEACE,CAEF,0BZ2xBJ,CYxxBI,wDAEE,wEZyxBN,CYnxBI,+BACE,UZqxBN,Cax0BA,WACE,uBAAA,CAAA,eAAA,CACA,KAAA,CACA,OAAA,CACA,MAAA,CACA,SAAA,CACA,gCAAA,CACA,2CAAA,CAGA,0DACE,CAEF,2Cbu0BF,Cal0BE,aAlBF,WAmBI,Ybq0BF,CACF,Cal0BE,iCACE,gEACE,CAEF,mGbk0BJ,Ca1zBE,iCACE,2BAAA,CACA,kGb4zBJ,CapzBE,kBACE,YAAA,CACA,kBAAA,CACA,ebszBJ,CalzBE,mBACE,iBAAA,CACA,SAAA,CACA,oBAAA,CACA,YAAA,CACA,aAAA,CACA,kBAAA,CACA,qBAAA,CACA,cAAA,CACA,uBbozBJ,CajzBI,kDAEE,UbkzBN,Ca9yBI,uCACE,YbgzBN,Ca5yBI,2BACE,YAAA,CACA,ab8yBN,CKvsBI,wCQzGA,2BAMI,Yb8yBN,CACF,Ca3yBM,8DAEE,aAAA,CACA,YAAA,CACA,aAAA,CACA,iBb6yBR,CKtuBI,mCQlEA,iCAII,YbwyBN,CACF,CaryBM,wCACE,YbuyBR,CahyBQ,+CACE,oBbkyBV,CKjvBI,sCQ3CA,iCAII,Yb4xBN,CACF,CavxBE,kBACE,iBAAA,CACA,YAAA,CACA,cAAA,CACA,8DbyxBJ,CapxBI,oCACE,UAAA,CACA,6BAAA,CACA,SAAA,CACA,8DACE,CAEF,mBboxBN,CajxBM,8CACE,8BbmxBR,Ca7wBE,kBACE,WAAA,CACA,aAAA,CACA,kBAAA,CACA,gBAAA,CACA,eAAA,CACA,kBb+wBJ,Ca5wBI,0DACE,UAAA,CACA,8BAAA,CACA,SAAA,CACA,8DACE,CAEF,mBb4wBN,CazwBM,oEACE,6Bb2wBR,CavwBM,4EACE,SAAA,CACA,uBAAA,CACA,SAAA,CACA,8DACE,CAEF,mBbuwBR,CalwBI,uCACE,iBAAA,CACA,UAAA,CACA,WbowBN,Ca/vBE,oBACE,YAAA,CACA,aAAA,CACA,cAAA,CACA,kBAAA,CACA,+CbiwBJ,Ca5vBI,2CACE,Yb8vBN,Ca1vBI,+DACE,WAAA,CACA,SAAA,CACA,oCb4vBN,CarvBE,mBACE,YbuvBJ,CKtzBI,mCQ8DF,mBAKI,aAAA,CACA,aAAA,CACA,iBAAA,CACA,gBbuvBJ,CapvBI,6BACE,iBAAA,CACA,absvBN,CACF,CKl0BI,sCQ8DF,mBAmBI,kBbqvBJ,CalvBI,6BACE,mBbovBN,CACF,Cc5+BA,WACE,+BAAA,CACA,0Cd++BF,Cc5+BE,aALF,WAMI,Yd++BF,CACF,Cc5+BE,kBACE,aAAA,CACA,ad8+BJ,Cc1+BE,iBACE,YAAA,CACA,kBAAA,CACA,oBAAA,CACA,uBd4+BJ,CK91BI,mCSlJF,iBAQI,Sd4+BJ,CACF,Ccz+BI,8CAEE,Ud0+BN,Cct+BI,uBACE,Udw+BN,CKt1BI,wCSnJA,uBAKI,Sdw+BN,Ccr+BM,yCACE,Ydu+BR,CACF,Ccn+BM,iCACE,Wdq+BR,Ccl+BQ,qCACE,oBdo+BV,Cc99BI,uBACE,WAAA,CACA,gBdg+BN,CKx2BI,wCS1HA,uBAMI,Sdg+BN,CACF,Cc79BM,iCACE,UAAA,CACA,ed+9BR,Cc59BQ,qCACE,oBd89BV,Ccv9BE,kBACE,iBAAA,CACA,WAAA,CACA,6BAAA,CACA,cAAA,CACA,eAAA,CACA,kBdy9BJ,Ccr9BE,mBACE,YAAA,CACA,adu9BJ,Ccn9BE,sBACE,iBAAA,CACA,OAAA,CACA,MAAA,CACA,gBAAA,CACA,cAAA,CACA,gBAAA,CACA,Udq9BJ,Cch9BA,gBACE,gDdm9BF,Cch9BE,uBACE,YAAA,CACA,cAAA,CACA,6BAAA,CACA,adk9BJ,Cc98BE,kCACE,sCdg9BJ,Cc78BI,gFAEE,+Bd88BN,Ccx8BA,qBACE,UAAA,CACA,iBAAA,CACA,eAAA,CACA,wCAAA,CACA,gBd28BF,CKp7BI,mCS5BJ,qBASI,Ud28BF,CACF,Ccv8BE,gCACE,sCdy8BJ,Ccp8BA,kBACE,cAAA,CACA,qBdu8BF,CKj8BI,mCSRJ,kBAMI,edu8BF,CACF,Ccp8BE,wBACE,oBAAA,CACA,YAAA,CACA,aAAA,CACA,iBds8BJ,Ccn8BI,+BACE,edq8BN,Ccj8BI,4BACE,gBAAA,CACA,mBAAA,CACA,iBdm8BN,CetnCA,MACE,0MAAA,CACA,gMAAA,CACA,yNfynCF,CennCA,QACE,eAAA,CACA,efsnCF,CennCE,eACE,aAAA,CACA,eAAA,CACA,eAAA,CACA,eAAA,CACA,sBfqnCJ,CelnCI,+BACE,YfonCN,CejnCM,mCACE,UAAA,CACA,WfmnCR,Ce5mCQ,sFAEE,aAAA,CACA,YAAA,CACA,aAAA,CACA,iBf8mCV,CevmCE,cACE,QAAA,CACA,SAAA,CACA,efymCJ,CermCE,cACE,efumCJ,CepmCI,4BACE,efsmCN,CenmCM,sCACE,mBAAA,CACA,cfqmCR,Ce/lCE,cACE,aAAA,CACA,iBAAA,CACA,eAAA,CACA,sBAAA,CACA,cAAA,CACA,sBAAA,CACA,uBfimCJ,Ce9lCI,kCACE,uCfgmCN,Ce5lCI,oCACE,+Bf8lCN,Ce1lCI,oCACE,af4lCN,CexlCI,wCAEE,+BfylCN,CerlCI,0CACE,YfulCN,CeplCM,yDACE,aAAA,CACA,UAAA,CACA,WAAA,CACA,qCAAA,CAAA,6BAAA,CACA,6BfslCR,Ce3kCE,kEACE,YfglCJ,CKrhCI,wCUpDA,0CAEE,iBAAA,CACA,KAAA,CACA,OAAA,CACA,MAAA,CACA,SAAA,CACA,YAAA,CACA,qBAAA,CACA,WAAA,CACA,2Cf2kCJ,CepkCI,+DAEE,eAAA,CACA,efskCN,CelkCI,gCACE,iBAAA,CACA,aAAA,CACA,wBAAA,CACA,uCAAA,CACA,eAAA,CACA,kBAAA,CACA,kBAAA,CACA,qDAAA,CACA,cfokCN,CejkCM,8CACE,iBAAA,CACA,SAAA,CACA,UAAA,CACA,aAAA,CACA,YAAA,CACA,aAAA,CACA,YfmkCR,CehkCQ,wDACE,WAAA,CACA,SfkkCV,Ce9jCQ,oDACE,aAAA,CACA,UAAA,CACA,WAAA,CACA,6BAAA,CACA,2CAAA,CAAA,mCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UfgkCV,Ce3jCM,8CACE,eAAA,CACA,2CAAA,CACA,gEACE,CACF,oCAAA,CAAA,gCAAA,CAAA,4BAAA,CACA,kBf4jCR,CezjCQ,2DACE,Yf2jCV,CetjCM,8CACE,gCAAA,CACA,2CfwjCR,CepjCM,yCACE,iBAAA,CACA,SAAA,CACA,UAAA,CACA,aAAA,CACA,YAAA,CACA,afsjCR,CenjCQ,mDACE,WAAA,CACA,SfqjCV,Ce/iCI,+BACE,MfijCN,Ce7iCI,+BACE,SAAA,CACA,4Df+iCN,Ce5iCM,qDACE,oBf8iCR,Ce3iCQ,+DACE,mBAAA,CACA,mBf6iCV,CexiCM,qDACE,+Bf0iCR,CeviCQ,sHAEE,+BfwiCV,CeliCI,+BACE,iBAAA,CACA,YAAA,CACA,mBfoiCN,CejiCM,6CACE,iBAAA,CACA,OAAA,CACA,WAAA,CACA,YAAA,CACA,aAAA,CACA,iBAAA,CACA,aAAA,CACA,gBfmiCR,CehiCQ,uDACE,UAAA,CACA,UfkiCV,Ce9hCQ,mDACE,aAAA,CACA,UAAA,CACA,WAAA,CACA,6BAAA,CACA,2CAAA,CAAA,mCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UfgiCV,CevhCM,+CACE,mBfyhCR,CejhCM,kDACE,efmhCR,Ce/gCM,4CACE,eAAA,CACA,wBfihCR,Ce9gCQ,0DACE,mBfghCV,Ce7gCU,oEACE,oBAAA,CACA,cf+gCZ,Ce1gCQ,kEACE,iBf4gCV,CezgCU,4EACE,kBAAA,CACA,cf2gCZ,CetgCQ,0EACE,mBfwgCV,CergCU,oFACE,oBAAA,CACA,cfugCZ,CelgCQ,kFACE,mBfogCV,CejgCU,4FACE,oBAAA,CACA,cfmgCZ,Ce3/BE,mBACE,wBf6/BJ,Cez/BE,wBACE,YAAA,CACA,0BAAA,CACA,SAAA,CACA,oEf2/BJ,Cet/BI,kCACE,2Bfw/BN,Cen/BE,gCACE,uBAAA,CACA,SAAA,CACA,qEfq/BJ,Ceh/BI,8CAEE,kCAAA,CAAA,0Bfi/BN,CACF,CK9sCI,wCUqOA,0CACE,aAAA,CACA,oBf4+BJ,Cez+BI,oDACE,mBAAA,CACA,mBf2+BN,Cev+BI,yDACE,Ufy+BN,Cer+BI,wDACE,Yfu+BN,Cen+BI,kDACE,Yfq+BN,Ceh+BE,gBACE,aAAA,CACA,eAAA,CACA,gCAAA,CACA,iDfk+BJ,CACF,CKhxCM,6DUqTF,6CACE,aAAA,CACA,oBAAA,CACA,sBf89BJ,Ce39BI,uDACE,mBAAA,CACA,mBf69BN,Cez9BI,4DACE,Uf29BN,Cev9BI,2DACE,Yfy9BN,Cer9BI,qDACE,Yfu9BN,CACF,CK9wCI,mCUkUE,6CACE,uBf+8BN,Ce38BI,gDACE,Yf68BN,CACF,CKtxCI,sCUzJJ,QAweI,oDf28BF,Cer8BI,8CACE,uBfu8BN,Ce77BE,sEACE,Yfk8BJ,Ce97BE,sEAEE,af+7BJ,Ce37BE,6CACE,Yf67BJ,Cez7BE,uBACE,aAAA,CACA,ef27BJ,Cex7BI,kCACE,ef07BN,Cet7BI,qCACE,Yfw7BN,Cep7BI,+BACE,afs7BN,Cen7BM,8CACE,aAAA,CACA,SAAA,CACA,mBAAA,CACA,uBfq7BR,Cej7BM,2DACE,Sfm7BR,Ce76BE,cACE,WAAA,CACA,WAAA,CACA,YAAA,CACA,yBf+6BJ,Ce56BI,wBACE,UAAA,CACA,wBf86BN,Ce16BI,oBACE,oBAAA,CACA,UAAA,CACA,WAAA,CACA,qBAAA,CACA,6BAAA,CACA,2CAAA,CAAA,mCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,Uf46BN,Cex6BI,0JAEE,uBfy6BN,Ce35BI,+HACE,Yfi6BN,Ce95BM,oDACE,aAAA,CACA,Sfg6BR,Ce75BQ,kEACE,Yf+5BV,Ce35BQ,2EACE,aAAA,CACA,eAAA,CACA,mBAAA,CACA,uBf65BV,Cex5BM,0DACE,mBf05BR,Cep5BI,2CACE,afs5BN,Cej5BE,qDACE,aAAA,CACA,oBAAA,CACA,mDfm5BJ,Ceh5BI,oEACE,Yfk5BN,CACF,CgB5hDA,MACE,igBhB+hDF,CgBzhDA,WACE,iBhB4hDF,CKl4CI,mCW3JJ,WAKI,ehB4hDF,CACF,CgBzhDE,kBACE,YhB2hDJ,CgBvhDE,oBACE,SAAA,CACA,ShByhDJ,CK33CI,wCWhKF,oBAMI,iBAAA,CACA,SAAA,CACA,YAAA,CACA,UAAA,CACA,WAAA,CACA,eAAA,CACA,2CAAA,CACA,kBAAA,CACA,uBAAA,CACA,4CACE,CAEF,mBhBuhDJ,CgBphDI,8BACE,aAAA,CACA,ShBshDN,CgBlhDI,+DACE,SAAA,CACA,oChBohDN,CACF,CKr6CI,mCW7IF,oBAqCI,cAAA,CACA,KAAA,CACA,MAAA,CACA,OAAA,CACA,QAAA,CACA,gCAAA,CACA,cAAA,CACA,sDhBihDJ,CgB3gDI,8BACE,OAAA,CACA,ShB6gDN,CgBzgDI,+DACE,UAAA,CAKA,YAAA,CACA,SAAA,CACA,4ChBugDN,CACF,CKx6CI,wCWxFA,+DAII,mBhBggDN,CACF,CKt9CM,6DW/CF,+DASI,mBhBggDN,CACF,CK39CM,6DW/CF,+DAcI,mBhBggDN,CACF,CgB3/CE,kBAEE,kCAAA,CAAA,0BhB4/CJ,CK17CI,wCWpEF,kBAMI,cAAA,CACA,KAAA,CACA,SAAA,CACA,SAAA,CACA,UAAA,CACA,WAAA,CACA,wBAAA,CACA,SAAA,CACA,mGhB4/CJ,CgBr/CI,6DACE,MAAA,CACA,uBAAA,CACA,SAAA,CACA,oGhBu/CN,CgBh/CM,uEACE,OAAA,CACA,ShBk/CR,CgB7+CI,iCACE,UAAA,CACA,SAAA,CACA,yBhB++CN,CACF,CKz+CI,mCWjDF,kBAgDI,iBAAA,CACA,WAAA,CACA,aAAA,CACA,eAAA,CACA,8ChB8+CJ,CgB3+CI,4BACE,UhB6+CN,CACF,CK3gDM,6DWkCF,6DAII,ahBy+CN,CACF,CK1/CI,sCWYA,6DASI,ahBy+CN,CACF,CgBp+CE,iBACE,iBhBs+CJ,CKlgDI,mCW2BF,iBAKI,mBhBs+CJ,CACF,CgBl+CE,kBACE,iBAAA,CACA,SAAA,CACA,yBAAA,CACA,sBAAA,CACA,2CAAA,CACA,gCAAA,CACA,2DhBo+CJ,CgB99CI,4BACE,yBhBg+CN,CgB59CI,6CACE,6BAAA,CAAA,qBhB89CN,CgB/9CI,oCACE,0BAAA,CAAA,qBhB89CN,CgB/9CI,yCACE,yBAAA,CAAA,qBhB89CN,CgB/9CI,+BACE,qBhB89CN,CgB19CI,6CAEE,uChB29CN,CgB79CI,oCAEE,uChB29CN,CgB79CI,yCAEE,uChB29CN,CgB79CI,kEAEE,uChB29CN,CgBv9CI,6BACE,YhBy9CN,CgBr9CI,6DACE,oChBu9CN,CK5gDI,wCWkBF,kBAwCI,UAAA,CACA,aAAA,CACA,ehBs9CJ,CACF,CKtiDI,mCWqCF,kBA+CI,UAAA,CACA,aAAA,CACA,mBAAA,CACA,aAAA,CACA,eAAA,CACA,gCAAA,CACA,mBhBs9CJ,CgBn9CI,4BACE,oBhBq9CN,CgBj9CI,mCACE,gChBm9CN,CgB/8CI,6CACE,uChBi9CN,CgBl9CI,oCACE,uChBi9CN,CgBl9CI,yCACE,uChBi9CN,CgBl9CI,+BACE,uChBi9CN,CgB78CI,wBACE,oChB+8CN,CgB38CI,6DACE,gCAAA,CACA,kBAAA,CACA,2CAAA,CACA,6BhB68CN,CgB18CM,wFAEE,uChB28CR,CgB78CM,+EAEE,uChB28CR,CgB78CM,oFAEE,uChB28CR,CgB78CM,wJAEE,uChB28CR,CACF,CgBr8CE,iBACE,iBAAA,CACA,SAAA,CACA,YAAA,CACA,aAAA,CACA,cAAA,CACA,kChBu8CJ,CgBl8CI,uBACE,UhBo8CN,CgBh8CI,+BACE,SAAA,CACA,UhBk8CN,CgB/7CM,yCACE,WAAA,CACA,ShBi8CR,CgB97CQ,6CACE,oBhBg8CV,CKzkDI,wCW8HA,+BAiBI,SAAA,CACA,UhB87CN,CgB37CM,yCACE,WAAA,CACA,ShB67CR,CgBz7CM,+CACE,YhB27CR,CACF,CKzmDI,mCWiJA,+BAkCI,mBhB07CN,CgBv7CM,8CACE,YhBy7CR,CACF,CgBp7CI,6BACE,SAAA,CACA,WAAA,CACA,oBAAA,CACA,SAAA,CACA,+DACE,CAEF,mBhBo7CN,CgBj7CM,uCACE,UAAA,CACA,UhBm7CR,CK1mDI,wCW0KA,6BAkBI,SAAA,CACA,WhBk7CN,CgB/6CM,uCACE,UAAA,CACA,UhBi7CR,CACF,CgB76CM,gGAEE,kBAAA,CACA,SAAA,CACA,mBhB86CR,CgB36CQ,sGACE,UhB66CV,CgBt6CE,mBACE,iBAAA,CACA,SAAA,CACA,UAAA,CACA,eAAA,CACA,6BhBw6CJ,CKnoDI,wCWsNF,mBASI,UAAA,CACA,QhBw6CJ,CACF,CK5pDI,mCWyOF,mBAeI,UAAA,CACA,SAAA,CACA,sBhBw6CJ,CgBr6CI,8DV/YJ,kGAAA,CUkZM,ShBs6CN,CACF,CgBj6CE,uBACE,WAAA,CACA,eAAA,CACA,2CAAA,CAEA,kCAAA,CAAA,0BAAA,CAIA,kBhB+5CJ,CgB55CI,kEAZF,uBAaI,uBhB+5CJ,CACF,CKzsDM,6DW4RJ,uBAkBI,ahB+5CJ,CACF,CKxrDI,sCWsQF,uBAuBI,ahB+5CJ,CACF,CK7rDI,mCWsQF,uBA4BI,YAAA,CACA,oBAAA,CACA,+DhB+5CJ,CgB55CI,kEACE,ehB85CN,CgB15CI,6BACE,qDhB45CN,CgBx5CI,0CACE,WAAA,CACA,YhB05CN,CgBt5CI,gDACE,oDhBw5CN,CgBr5CM,sDACE,0ChBu5CR,CACF,CgBh5CA,kBACE,gCAAA,CACA,qBhBm5CF,CgBh5CE,wBACE,eAAA,CACA,uCAAA,CACA,gBAAA,CACA,kBAAA,CACA,qDAAA,CACA,uBhBk5CJ,CKjuDI,mCWyUF,wBAUI,mBhBk5CJ,CgB/4CI,kCACE,oBAAA,CACA,chBi5CN,CACF,CgB54CE,wBACE,QAAA,CACA,SAAA,CACA,ehB84CJ,CgB14CE,wBACE,2DhB44CJ,CgBz4CI,oCACE,ehB24CN,CgBt4CE,wBACE,aAAA,CACA,YAAA,CACA,gCAAA,CACA,uBhBw4CJ,CgBr4CI,4DAEE,uDhBs4CN,CgBl4CI,gDACE,mBhBo4CN,CgB/3CE,gCACE,aAAA,CACA,mBAAA,CACA,+BAAA,CACA,gBAAA,CACA,SAAA,CACA,cAAA,CACA,2CACE,CAEF,uBhB+3CJ,CK3wDI,mCWkYF,gCAcI,mBhB+3CJ,CgB53CI,0CACE,oBAAA,CACA,kBhB83CN,CACF,CgB13CI,4EAEE,+BAAA,CACA,uDhB23CN,CgBv3CI,gGAEE,YhBw3CN,CgBp3CI,oCACE,WhBs3CN,CgBj3CE,2BACE,iBAAA,CACA,eAAA,CACA,ehBm3CJ,CKnyDI,mCW6aF,2BAOI,mBhBm3CJ,CgBh3CI,qCACE,oBAAA,CACA,kBhBk3CN,CACF,CgB32CM,8DACE,eAAA,CACA,eAAA,CACA,eAAA,CACA,ehB62CR,CgBv2CE,wBACE,iBAAA,CACA,MAAA,CACA,YAAA,CACA,aAAA,CACA,YAAA,CACA,uChBy2CJ,CKvyDI,wCWwbF,wBAUI,YhBy2CJ,CACF,CgBt2CI,8BACE,oBAAA,CACA,UAAA,CACA,WAAA,CACA,6BAAA,CACA,+CAAA,CAAA,uCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UhBw2CN,CgBp2CI,kCACE,OAAA,CACA,ShBs2CN,CgBn2CM,wCACE,oBhBq2CR,CgB/1CE,yBACE,aAAA,CACA,eAAA,CACA,gBAAA,CACA,ehBi2CJ,CgB71CE,0BACE,mBAAA,CACA,eAAA,CACA,aAAA,CACA,eAAA,CACA,uCAAA,CACA,gBAAA,CACA,eAAA,CACA,sBAAA,CACA,2BAAA,CACA,oBhB+1CJ,CK/0DI,wCWseF,0BAcI,eAAA,CACA,oBhB+1CJ,CACF,CK93DM,6DW+gBJ,0BAoBI,eAAA,CACA,oBhB+1CJ,CACF,CgB51CI,+BACE,yBAAA,CACA,wBhB81CN,CgBz1CE,yBACE,aAAA,CACA,gBAAA,CACA,iBhB21CJ,CgBv1CE,uBACE,+BAAA,CACA,wBhBy1CJ,CiB7hEA,WACE,iBAAA,CACA,SjBgiEF,CiB7hEE,kBACE,iBAAA,CACA,sBAAA,CACA,QAAA,CACA,YAAA,CACA,gBAAA,CACA,gCAAA,CACA,2CAAA,CACA,mBAAA,CACA,kEACE,CAEF,mCAAA,CACA,SAAA,CACA,oEjB6hEJ,CiBvhEI,6EAEE,gBAAA,CACA,+BAAA,CACA,SAAA,CACA,+EjBwhEN,CiBjhEI,wBACE,iBAAA,CACA,KAAA,CACA,QAAA,CACA,OAAA,CACA,QAAA,CACA,iBAAA,CACA,kBAAA,CACA,mCAAA,CAAA,oCAAA,CACA,YAAA,CACA,qCAAA,CAAA,8CAAA,CACA,UjBmhEN,CiB9gEE,iBACE,kBAAA,CACA,QAAA,CACA,SAAA,CACA,aAAA,CACA,eAAA,CACA,oBAAA,CACA,mBjBghEJ,CiB5gEE,iBACE,kBjB8gEJ,CiB1gEE,iBACE,aAAA,CACA,UAAA,CACA,oBAAA,CACA,kBAAA,CACA,cAAA,CACA,2CACE,CAEF,uBjB0gEJ,CiBvgEI,2BACE,mBAAA,CACA,mBjBygEN,CiBrgEI,8CAEE,qDjBsgEN,CkB/lEA,YACE,uBAAA,CAAA,eAAA,CACA,UAAA,CACA,aAAA,CACA,qBAAA,CACA,aAAA,CACA,gBlBkmEF,CkB/lEE,aATF,YAUI,YlBkmEF,CACF,CKx7DI,wCapKA,qBACE,cAAA,CACA,KAAA,CACA,aAAA,CACA,SAAA,CACA,aAAA,CACA,aAAA,CACA,WAAA,CACA,2CAAA,CACA,uBAAA,CACA,iElB+lEJ,CkB1lEI,+BACE,cAAA,CACA,SlB4lEN,CkBxlEI,mEZhBJ,sGAAA,CYmBM,6BlBylEN,CkBtlEM,6EACE,8BlBwlER,CkBnlEI,6CACE,iBAAA,CACA,KAAA,CACA,OAAA,CACA,QAAA,CACA,MAAA,CACA,QAAA,CACA,6BAAA,CAAA,yBAAA,CAAA,qBAAA,CACA,elBqlEN,CACF,CK9+DI,sCalKJ,YAiEI,QlBmlEF,CkBhlEE,mBACE,WlBklEJ,CACF,CkB9kEE,uBACE,YAAA,CACA,OlBglEJ,CK1/DI,mCaxFF,uBAMI,QlBglEJ,CkB7kEI,8BACE,WlB+kEN,CkB3kEI,qCACE,alB6kEN,CkBzkEI,+CACE,kBlB2kEN,CACF,CkBtkEE,wBACE,cAAA,CACA,eAAA,CAEA,kCAAA,CAAA,0BAAA,CAKA,oBAAA,CACA,+DlBmkEJ,CkBhkEI,8BACE,qDlBkkEN,CkB9jEI,2CACE,WAAA,CACA,YlBgkEN,CkB5jEI,iDACE,oDlB8jEN,CkB3jEM,uDACE,0ClB6jER,CKzgEI,wCa1CF,YACE,cAAA,CACA,KAAA,CACA,SAAA,CACA,OAAA,CACA,QAAA,CACA,gCAAA,CACA,SAAA,CACA,sDlBujEF,CkBjjEE,4CACE,UAAA,CACA,WAAA,CACA,SAAA,CACA,4ClBmjEJ,CACF,CDjtEA,0CACE,GACE,QCmtEF,CDhtEA,GACE,aCktEF,CACF,CDztEA,kCACE,GACE,QCmtEF,CDhtEA,GACE,aCktEF,CACF,CD9sEA,yCACE,GACE,0BAAA,CACA,SCgtEF,CD7sEA,IACE,SC+sEF,CD5sEA,GACE,uBAAA,CACA,SC8sEF,CACF,CD3tEA,iCACE,GACE,0BAAA,CACA,SCgtEF,CD7sEA,IACE,SC+sEF,CD5sEA,GACE,uBAAA,CACA,SC8sEF,CACF,CDtsEA,WACE,aAAA,CACA,gBAAA,CACA,eAAA,CACA,kBAAA,CAEA,kCAAA,CAAA,0BAAA,CACA,uBCusEF,CDpsEE,kCAEE,UCqsEJ,CDjsEE,iBACE,oBAAA,CACA,YAAA,CACA,aAAA,CACA,qBCmsEJ,CDhsEI,qBACE,gBAAA,CACA,iBCksEN,CD/rEM,+BACE,kBAAA,CACA,aCisER,CD5rEI,wCACE,iBAAA,CACA,iBC8rEN,CD3rEM,kDACE,kBAAA,CACA,aAAA,CACA,kBAAA,CACA,cC6rER,CDvrEE,uBACE,oBAAA,CACA,6BAAA,CACA,iBAAA,CACA,eAAA,CACA,eAAA,CACA,sBAAA,CACA,qBCyrEJ,CDrrEE,kBACE,QAAA,CACA,SAAA,CACA,eAAA,CACA,eAAA,CACA,gBAAA,CACA,oBAAA,CACA,WCurEJ,CDprEI,uCACE,qDAAA,CAAA,6CCsrEN,CDjrEE,iBACE,UCmrEJ,CDhrEI,2BACE,WCkrEN,CD9qEI,sCACE,oDAAA,CAAA,4CCgrEN,CD5qEI,wBACE,cAAA,CACA,WC8qEN,CD1qEI,oCACE,YC4qEN,CmB9yEA,SACE,UAAA,CACA,aAAA,CACA,gCAAA,CACA,2CAAA,CACA,gCnBizEF,CmB9yEE,aARF,SASI,YnBizEF,CACF,CKtoEI,wCcrLJ,SAcI,YnBizEF,CACF,CmB9yEE,+BACE,mBnBgzEJ,CmB5yEE,eAEE,kBAAA,CACA,SAAA,CACA,kBAAA,CACA,eAAA,CACA,enB8yEJ,CmB3yEI,yBACE,kBAAA,CACA,anB6yEN,CmBxyEE,eACE,oBAAA,CACA,aAAA,CACA,mBAAA,CACA,kBnB0yEJ,CmBryEE,eACE,aAAA,CACA,gBAAA,CACA,eAAA,CAEA,kCAAA,CAAA,0BAAA,CACA,UAAA,CACA,8DnBsyEJ,CmBjyEI,iEAGE,aAAA,CACA,SnBiyEN,CmB5xEM,2CACE,qBnB8xER,CmB/xEM,2CACE,qBnBiyER,CmBlyEM,2CACE,qBnBoyER,CmBryEM,2CACE,qBnBuyER,CmBxyEM,2CACE,oBnB0yER,CmB3yEM,2CACE,qBnB6yER,CmB9yEM,2CACE,qBnBgzER,CmBjzEM,2CACE,qBnBmzER,CmBpzEM,4CACE,qBnBszER,CmBvzEM,4CACE,oBnByzER,CmB1zEM,4CACE,qBnB4zER,CmB7zEM,4CACE,qBnB+zER,CmBh0EM,4CACE,qBnBk0ER,CmBn0EM,4CACE,qBnBq0ER,CmBt0EM,4CACE,oBnBw0ER,CmBl0EI,8CACE,yBAAA,CACA,SAAA,CACA,wCnBo0EN,CoBn5EA,MACE,iQpBs5EF,CoBh5EA,YACE,aAAA,CACA,aAAA,CACA,epBm5EF,CoBh5EE,qBACE,iBAAA,CAKA,UAAA,CACA,kBAAA,CACA,kBpB84EJ,CoB34EI,+BACE,mBAAA,CACA,iBpB64EN,CoBz4EI,2BACE,oBAAA,CACA,WAAA,CACA,YAAA,CACA,iBAAA,CACA,6BAAA,CACA,yCAAA,CAAA,iCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,UpB24EN,CoBx4EM,qCACE,kBAAA,CACA,apB04ER,CoBp4EE,kBACE,iBAAA,CACA,UAAA,CACA,SAAA,CACA,iBAAA,CACA,kBAAA,CACA,SAAA,CACA,aAAA,CACA,gCAAA,CACA,oBAAA,CACA,2CAAA,CACA,mBAAA,CACA,kEACE,CAEF,SAAA,CACA,+CACE,CAEF,oCAAA,CAAA,gCAAA,CAAA,4BpBk4EJ,CoB/3EI,uDAEE,gBAAA,CACA,SAAA,CACA,uCpBg4EN,CoBz3EE,kBACE,kBpB23EJ,CoBv3EE,kBACE,aAAA,CACA,UAAA,CACA,oBAAA,CACA,kBAAA,CACA,kBAAA,CACA,cAAA,CACA,2CACE,CAEF,uBpBu3EJ,CoBp3EI,4BACE,mBAAA,CACA,mBpBs3EN,CoBl3EI,gDAEE,qDpBm3EN,CqB38EA,MAEI,2RAAA,CAAA,4MAAA,CAAA,sPAAA,CAAA,8xBAAA,CAAA,mQAAA,CAAA,ibAAA,CAAA,gMAAA,CAAA,kUAAA,CAAA,0VAAA,CAAA,0eAAA,CAAA,kUAAA,CAAA,gMrBo+EJ,CqBz9EE,4CACE,iBAAA,CACA,eAAA,CACA,eAAA,CACA,mCAAA,CACA,gBAAA,CACA,uBAAA,CACA,8CAAA,CACA,+BAAA,CACA,mBAAA,CACA,yErB49EJ,CqBv9EI,aAfF,4CAgBI,erB09EJ,CACF,CqBv9EI,gEACE,gCAAA,CACA,gBrBy9EN,CqBr9EI,gIACE,YrBu9EN,CqBn9EI,4FACE,iBrBq9EN,CqBj9EI,kFACE,erBm9EN,CqB/8EI,0FACE,YrBi9EN,CqB78EI,8EACE,mBrB+8EN,CqB18EE,kDACE,iBAAA,CACA,wBAAA,CACA,8BAAA,CACA,eAAA,CACA,oCAAA,CACA,+BrB48EJ,CqBz8EI,sEACE,wBAAA,CACA,8BAAA,CACA,gCAAA,CACA,gBrB28EN,CqBv8EI,kFACE,erBy8EN,CqBr8EI,gEACE,iBAAA,CACA,UAAA,CACA,UAAA,CACA,WAAA,CACA,wBCyIU,CDxIV,kDAAA,CAAA,0CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UrBu8EN,CqBp8EM,oFACE,WAAA,CACA,SrBs8ER,CqBh8EI,4DACE,cAAA,CACA,eAAA,CACA,kBAAA,CACA,wBAAA,CACA,qBAAA,CACA,erBk8EN,CqB77EI,gGACE,YrB+7EN,CqBj7EE,sDACE,oBrBo7EJ,CqBh7EE,8DACE,oCAAA,CACA,oBrBm7EJ,CqBh7EI,4EACE,wBAdG,CAeH,kDAAA,CAAA,0CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrBk7EN,CqBh8EE,gLACE,oBrBm8EJ,CqB/7EE,wMACE,mCAAA,CACA,oBrBk8EJ,CqB/7EI,kPACE,wBAdG,CAeH,sDAAA,CAAA,8CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrBi8EN,CqB/8EE,4GACE,oBrBk9EJ,CqB98EE,4HACE,mCAAA,CACA,oBrBi9EJ,CqB98EI,wJACE,wBAdG,CAeH,kDAAA,CAAA,0CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrBg9EN,CqB99EE,0KACE,oBrBi+EJ,CqB79EE,kMACE,mCAAA,CACA,oBrBg+EJ,CqB79EI,4OACE,wBAdG,CAeH,iDAAA,CAAA,yCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB+9EN,CqB7+EE,0KACE,oBrBg/EJ,CqB5+EE,kMACE,kCAAA,CACA,oBrB++EJ,CqB5+EI,4OACE,wBAdG,CAeH,qDAAA,CAAA,6CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB8+EN,CqB5/EE,wKACE,oBrB+/EJ,CqB3/EE,gMACE,oCAAA,CACA,oBrB8/EJ,CqB3/EI,0OACE,wBAdG,CAeH,sDAAA,CAAA,8CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB6/EN,CqB3gFE,wLACE,oBrB8gFJ,CqB1gFE,gNACE,mCAAA,CACA,oBrB6gFJ,CqB1gFI,0PACE,wBAdG,CAeH,qDAAA,CAAA,6CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB4gFN,CqB1hFE,8KACE,oBrB6hFJ,CqBzhFE,sMACE,mCAAA,CACA,oBrB4hFJ,CqBzhFI,gPACE,wBAdG,CAeH,qDAAA,CAAA,6CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB2hFN,CqBziFE,kHACE,oBrB4iFJ,CqBxiFE,kIACE,mCAAA,CACA,oBrB2iFJ,CqBxiFI,8JACE,wBAdG,CAeH,oDAAA,CAAA,4CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrB0iFN,CqBxjFE,oDACE,oBrB2jFJ,CqBvjFE,4DACE,kCAAA,CACA,oBrB0jFJ,CqBvjFI,0EACE,wBAdG,CAeH,iDAAA,CAAA,yCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrByjFN,CqBvkFE,4DACE,oBrB0kFJ,CqBtkFE,oEACE,oCAAA,CACA,oBrBykFJ,CqBtkFI,kFACE,wBAdG,CAeH,qDAAA,CAAA,6CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrBwkFN,CqBtlFE,8GACE,oBrBylFJ,CqBrlFE,8HACE,kCAAA,CACA,oBrBwlFJ,CqBrlFI,0JACE,wBAdG,CAeH,mDAAA,CAAA,2CAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBrBulFN,CuB9vFA,MACE,wMvBiwFF,CuBxvFE,kCACE,mBAAA,CACA,kBAAA,CACA,kBvB2vFJ,CuBvvFE,+BACE,mBAAA,CACA,mBAAA,CACA,mBvByvFJ,CuBrvFE,sBACE,uCAAA,CACA,gBvBuvFJ,CuBpvFI,yBACE,avBsvFN,CuBlvFI,yBACE,sBvBovFN,CuBjvFM,gCACE,gCvBmvFR,CuB/uFM,mGAEE,uBAAA,CACA,SvBgvFR,CuB5uFM,sCACE,YvB8uFR,CuBxuFE,8BACE,oBAAA,CACA,+BAAA,CAEA,WAAA,CACA,0BAAA,CACA,4BAAA,CACA,SAAA,CACA,4DvByuFJ,CuBnuFI,aAdF,8BAeI,+BAAA,CACA,uBAAA,CACA,SvBsuFJ,CACF,CuBnuFI,wCACE,6BvBquFN,CuBjuFI,oCACE,+BvBmuFN,CuB/tFI,qCACE,oBAAA,CACA,WAAA,CACA,YAAA,CACA,6BAAA,CACA,2CAAA,CAAA,mCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UvBiuFN,CuB3tFQ,mDACE,oBvB6tFV,CwBj0FE,wBACE,oBAAA,CACA,iBAAA,CACA,yCAAA,CACA,SAAA,CACA,mCxBo0FJ,CwB/zFI,aAVF,wBAWI,YxBk0FJ,CACF,CwB/zFI,kCACE,kBAAA,CACA,axBi0FN,CwB5zFE,6FAGE,SAAA,CACA,mCxB8zFJ,CwBxzFE,4FAGE,+BxB0zFJ,CwBnzFE,oBACE,wBxBqzFJ,CwBjzFE,kEAGE,mBxBmzFJ,CwBhzFI,uFACE,aAAA,CACA,kBAAA,CACA,kBAAA,CACA,UxBozFN,CwB/yFE,sBACE,mBxBizFJ,CwB9yFI,6BACE,aAAA,CACA,mBAAA,CACA,mBAAA,CACA,UxBgzFN,CwB3yFE,4CAEE,mBxB6yFJ,CwB1yFI,0DACE,aAAA,CACA,kBAAA,CACA,kBAAA,CACA,UxB6yFN,CyBj4FE,2BACE,azBo4FJ,CKntFI,wCoBlLF,2BAKI,ezBo4FJ,CACF,CyBj4FI,6BACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CAEA,yBAAA,CACA,eAAA,CACA,iBzBk4FN,C0Bh5FE,0EAGE,kCAAA,CAAA,0B1Bm5FJ,C0B/4FE,uBACE,4C1Bi5FJ,C0B74FE,uBACE,4C1B+4FJ,C0B34FE,4BACE,qC1B64FJ,C0B14FI,mCACE,a1B44FN,C0Bx4FI,kCACE,a1B04FN,C0Br4FE,0BACE,aAAA,CACA,YAAA,CACA,mBAAA,CACA,kBAAA,CACA,aAAA,CACA,e1Bu4FJ,C0Bp4FI,uCACE,e1Bs4FN,C0Bl4FI,sCACE,kB1Bo4FN,C2Bt7FA,MACE,8L3By7FF,C2Bh7FE,oBAGE,iBAAA,CACA,aAAA,CACA,gB3Bi7FJ,C2B96FI,wCACE,uB3Bg7FN,C2B56FI,gCACE,gBAAA,CACA,e3B86FN,C2Bx6FM,wCACE,mB3B06FR,C2Br6FI,0BACE,aAAA,CACA,U3Bu6FN,C2Bl6FE,oBAGE,aAAA,CACA,eAAA,CACA,+BAAA,CACA,4BAAA,CACA,6BAAA,CACA,c3Bk6FJ,C2B/5FI,8BACE,iC3Bi6FN,C2B75FI,wCACE,YAAA,CACA,uC3B+5FN,C2B35FI,0BACE,iBAAA,CACA,SAAA,CACA,WAAA,CACA,UAAA,CACA,WAAA,CACA,6BAAA,CACA,yCAAA,CAAA,iCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,sBAAA,CACA,yBAAA,CACA,U3B65FN,C2B15FM,oCACE,UAAA,CACA,UAAA,CACA,wB3B45FR,C2Bv5FI,wEAEE,Y3Bw5FN,C4Bh/FE,+DAGE,mBAAA,CACA,cAAA,CACA,uB5Bm/FJ,C4Bh/FI,2EACE,aAAA,CACA,eAAA,CACA,iB5Bo/FN,C6BjgGE,6BAEE,sC7BogGJ,C6BjgGE,cACE,yC7BmgGJ,C6BhgGE,sIASE,oC7BkgGJ,C6B//FE,2EAKE,qC7BigGJ,C6B9/FE,wGAOE,oC7BggGJ,C6B7/FE,yFAME,qC7B+/FJ,C6B5/FE,6BAEE,kC7B8/FJ,C6B3/FE,6CAGE,sC7B6/FJ,C6B1/FE,4DAIE,sC7B4/FJ,C6Bz/FE,4DAIE,qC7B2/FJ,C6Bx/FE,yFAME,qC7B0/FJ,C6Bv/FE,2EAKE,sC7By/FJ,C6Bt/FE,wHAQE,qC7Bw/FJ,C6Br/FE,8BAEE,gBAAA,CACA,gBAAA,CACA,mB7Bu/FJ,C6Bp/FE,eACE,4C7Bs/FJ,C6Bn/FE,eACE,4C7Bq/FJ,C6Bj/FE,gBACE,aAAA,CACA,wBAAA,CACA,wBAAA,CACA,wC7Bm/FJ,C6B/+FE,iCACE,uBAAA,CAAA,eAAA,CACA,oBAAA,CACA,UAAA,CACA,2BAAA,CACA,2BAAA,CACA,2BAAA,CACA,uCAAA,CACA,wCAAA,CACA,+DAAA,CACA,0BAAA,CACA,wBAAA,CAAA,qBAAA,CAAA,oBAAA,CAAA,gB7Bi/FJ,C6Bx+FA,gBACE,iBAAA,CACA,e7B2+FF,C6Bv+FE,yCAEE,aAAA,CACA,S7By+FJ,C6Bp+FE,mBACE,Y7Bs+FJ,C6Bj+FE,oBACE,Q7Bm+FJ,C6B99FE,yBAEE,oDAAA,CACA,eAAA,CACA,wCAAA,CACA,wBAAA,CAAA,qBAAA,CAAA,oBAAA,CAAA,gB7Bg+FJ,C6B59FE,2BACE,2BAAA,CACA,+D7B89FJ,C6B39FI,+BACE,uCAAA,CACA,gB7B69FN,C6Bx9FE,sBACE,MAAA,CACA,e7B09FJ,C6Bh9FE,4BACE,YAAA,CACA,aAAA,CACA,mB7Bm9FJ,C6Bh9FI,iCACE,e7Bk9FN,CKj/FI,wCwBuCA,uBACE,iB7B68FJ,C6B18FI,4BACE,eAAA,CACA,e7B48FN,C6Bx8FI,4BACE,e7B08FN,C6Br8FE,4BACE,iBAAA,CACA,e7Bu8FJ,C6Bp8FI,iCACE,eAAA,CACA,e7Bs8FN,CACF,C8BprGI,yDAEE,iBAAA,CACA,QAAA,CACA,aAAA,CACA,+BAAA,CACA,8B9BurGN,C8BnrGI,uBACE,cAAA,CACA,uC9BqrGN,C8BhoGQ,iHACE,kBAAA,CACA,W9B0oGV,C8B5oGQ,6HACE,kBAAA,CACA,W9BspGV,C8BxpGQ,6HACE,kBAAA,CACA,W9BkqGV,C8BpqGQ,oHACE,kBAAA,CACA,W9B8qGV,C8BhrGQ,0HACE,kBAAA,CACA,W9B0rGV,C8B5rGQ,uHACE,kBAAA,CACA,W9BssGV,C8BxsGQ,uHACE,kBAAA,CACA,W9BktGV,C8BptGQ,6HACE,kBAAA,CACA,W9B8tGV,C8BhuGQ,yCACE,kBAAA,CACA,W9BkuGV,C8BpuGQ,yCACE,kBAAA,CACA,W9BsuGV,C8BxuGQ,0CACE,kBAAA,CACA,W9B0uGV,C8B5uGQ,uCACE,kBAAA,CACA,W9B8uGV,C8BhvGQ,wCACE,kBAAA,CACA,W9BkvGV,C8BpvGQ,sCACE,kBAAA,CACA,W9BsvGV,C8BxvGQ,wCACE,kBAAA,CACA,W9B0vGV,C8B5vGQ,oCACE,kBAAA,CACA,W9B8vGV,C8BhwGQ,2CACE,kBAAA,CACA,W9BkwGV,C8BpwGQ,qCACE,kBAAA,CACA,W9BswGV,C8BxwGQ,oCACE,kBAAA,CACA,W9B0wGV,C8B5wGQ,kCACE,kBAAA,CACA,W9B8wGV,C8BhxGQ,qCACE,kBAAA,CACA,W9BkxGV,C8BpxGQ,mCACE,kBAAA,CACA,W9BsxGV,C8BxxGQ,qCACE,kBAAA,CACA,W9B0xGV,C8B5xGQ,wCACE,kBAAA,CACA,W9B8xGV,C8BhyGQ,sCACE,kBAAA,CACA,W9BkyGV,C8BpyGQ,2CACE,kBAAA,CACA,W9BsyGV,C8B1xGQ,iCACE,iBAAA,CACA,W9B4xGV,C8B9xGQ,uCACE,iBAAA,CACA,W9BgyGV,C8BlyGQ,mCACE,iBAAA,CACA,W9BoyGV,C+Bx3GE,4BACE,YAAA,CACA,QAAA,CACA,UAAA,CACA,yD/B23GJ,C+Bx3GI,aAPF,4BAQI,aAAA,CACA,O/B23GJ,CACF,C+Bv3GI,wJAGE,Q/By3GN,C+Bt3GM,uKACE,wBAAA,CACA,yB/B03GR,C+Br3GI,wCACE,Q/Bu3GN,C+Bl3GE,wBACE,iBAAA,CACA,YAAA,CACA,cAAA,CACA,YAAA,CACA,mB/Bo3GJ,C+B92GI,8BACE,iBAAA,CACA,OAAA,CACA,QAAA,CACA,S/Bg3GN,C+B72GM,4CACE,+BAAA,CACA,sC/B+2GR,C+B52GQ,4DACE,a/B82GV,C+Bz2GM,0CACE,kB/B22GR,C+Bv2GM,wDACE,YAAA,CACA,uC/By2GR,C+Bp2GI,8BACE,SAAA,CACA,UAAA,CACA,+BAAA,CACA,uCAAA,CACA,eAAA,CACA,gBAAA,CACA,qCAAA,CACA,cAAA,CACA,qB/Bs2GN,C+Bn2GM,oCACE,+B/Bq2GR,CgC/7GA,MACE,mVAAA,CAEA,4VhCm8GF,CgCz7GE,4BACE,iBAAA,CACA,oBhC47GJ,CgCx7GI,4CACE,iBAAA,CACA,SAAA,CACA,ShC07GN,CgCv7GM,sDACE,UAAA,CACA,ShCy7GR,CgCn7GE,+CACE,UAAA,CACA,ShCq7GJ,CgCj7GE,wCACE,iBAAA,CACA,SAAA,CACA,WAAA,CACA,YAAA,CACA,aAAA,CACA,qDAAA,CACA,0CAAA,CAAA,kCAAA,CACA,6BAAA,CAAA,qBAAA,CACA,yBAAA,CAAA,iBAAA,CACA,UhCm7GJ,CgCh7GI,kDACE,YAAA,CACA,ShCk7GN,CgC76GE,gEACE,wBV8Va,CU7Vb,mDAAA,CAAA,2ChC+6GJ,CK10GI,mC4B5JA,oBACE,UAAA,CACA,aAAA,CACA,YAAA,CACA,kBAAA,CACA,mBjC0+GJ,CiCh+GI,sDACE,WAAA,CACA,cAAA,CACA,iBjCu+GN,CiCp+GM,kCACE,UAAA,CACA,kBAAA,CACA,ajCs+GR,CACF","file":"src/assets/stylesheets/main.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Keyframes\n// ----------------------------------------------------------------------------\n\n// Show repository facts\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n\n  100% {\n    height: px2rem(13px);\n  }\n}\n\n// Show repository fact\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n\n  50% {\n    opacity: 0;\n  }\n\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Repository information\n.md-source {\n  display: block;\n  font-size: px2rem(13px);\n  line-height: 1.2;\n  white-space: nowrap;\n  // Hack: promote to own layer to reduce jitter\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n\n  // Repository information on focus/hover\n  &:focus,\n  &:hover {\n    opacity: 0.7;\n  }\n\n  // Repository icon\n  &__icon {\n    display: inline-block;\n    width: px2rem(48px);\n    height: px2rem(48px);\n    vertical-align: middle;\n\n    // Align with margin only (as opposed to normal button alignment)\n    svg {\n      margin-top: px2rem(12px);\n      margin-left: px2rem(12px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(12px);\n        margin-left: initial;\n      }\n    }\n\n    // Adjust spacing if icon is present\n    + .md-source__repository {\n      margin-left: px2rem(-40px);\n      padding-left: px2rem(40px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(-40px);\n        margin-left: initial;\n        padding-right: px2rem(40px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Repository name\n  &__repository {\n    display: inline-block;\n    max-width: calc(100% - #{px2rem(24px)});\n    margin-left: px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n    vertical-align: middle;\n  }\n\n  // Repository facts\n  &__facts {\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n    font-weight: 700;\n    font-size: px2rem(11px);\n    list-style-type: none;\n    opacity: 0.75;\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__facts--done 250ms ease-in;\n    }\n  }\n\n  // Repository fact\n  &__fact {\n    float: left;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: right;\n    }\n\n    // Show after the data was loaded\n    [data-md-state=\"done\"] & {\n      animation: md-source__fact--done 400ms ease-out;\n    }\n\n    // Middle dot before fact\n    &::before {\n      margin: 0 px2rem(2px);\n      content: \"\\00B7\";\n    }\n\n    // Remove middle dot on first fact\n    &:first-child::before {\n      display: none;\n    }\n  }\n}\n","@charset \"UTF-8\";\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n}\n\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\na {\n  color: inherit;\n  text-decoration: none;\n}\n\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: 0.05rem;\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  line-height: 1em;\n}\n\nimg {\n  border-style: none;\n}\n\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\ninput {\n  border: 0;\n  outline: none;\n}\n\n:root {\n  --md-default-fg-color: hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color: hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n:root > * {\n  --md-code-fg-color: hsla(200, 18%, 26%, 1);\n  --md-code-bg-color: hsla(0, 0%, 96%, 1);\n  --md-code-hl-color: hsla(60, 100%, 50%, 0.5);\n  --md-code-hl-number-color: hsla(0, 67%, 50%, 1);\n  --md-code-hl-special-color: hsla(340, 83%, 47%, 1);\n  --md-code-hl-function-color: hsla(291, 45%, 50%, 1);\n  --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);\n  --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);\n  --md-code-hl-string-color: hsla(150, 63%, 30%, 1);\n  --md-code-hl-name-color: var(--md-code-fg-color);\n  --md-code-hl-operator-color: var(--md-default-fg-color--light);\n  --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n  --md-code-hl-comment-color: var(--md-default-fg-color--light);\n  --md-code-hl-generic-color: var(--md-default-fg-color--light);\n  --md-code-hl-variable-color: var(--md-default-fg-color--light);\n  --md-typeset-color: var(--md-default-fg-color);\n  --md-typeset-a-color: var(--md-primary-fg-color);\n  --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5);\n  --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);\n  --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);\n  --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);\n  --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);\n  --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);\n  --md-admonition-fg-color: var(--md-default-fg-color);\n  --md-admonition-bg-color: var(--md-default-bg-color);\n  --md-footer-fg-color: hsla(0, 0%, 100%, 1);\n  --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);\n  --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);\n}\n\n.md-icon svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family: var(--md-text-font-family, _), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family: var(--md-code-font-family, _), SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n.md-typeset {\n  font-size: 0.8rem;\n  line-height: 1.6;\n  color-adjust: exact;\n}\n@media print {\n  .md-typeset {\n    font-size: 0.68rem;\n  }\n}\n.md-typeset ul,\n.md-typeset ol,\n.md-typeset dl,\n.md-typeset figure,\n.md-typeset blockquote,\n.md-typeset pre {\n  display: flow-root;\n  margin: 1em 0;\n}\n.md-typeset h1 {\n  margin: 0 0 1.25em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 300;\n  font-size: 2em;\n  line-height: 1.3;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 {\n  margin: 1.6em 0 0.64em;\n  font-weight: 300;\n  font-size: 1.5625em;\n  line-height: 1.4;\n  letter-spacing: -0.01em;\n}\n.md-typeset h3 {\n  margin: 1.6em 0 0.8em;\n  font-weight: 400;\n  font-size: 1.25em;\n  line-height: 1.5;\n  letter-spacing: -0.01em;\n}\n.md-typeset h2 + h3 {\n  margin-top: 0.8em;\n}\n.md-typeset h4 {\n  margin: 1em 0;\n  font-weight: 700;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5,\n.md-typeset h6 {\n  margin: 1.25em 0;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.8em;\n  letter-spacing: -0.01em;\n}\n.md-typeset h5 {\n  text-transform: uppercase;\n}\n.md-typeset hr {\n  display: flow-root;\n  margin: 1.5em 0;\n  border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset a {\n  color: var(--md-typeset-a-color);\n  word-break: break-word;\n}\n.md-typeset a, .md-typeset a::before {\n  transition: color 125ms;\n}\n.md-typeset a:focus, .md-typeset a:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n  color: var(--md-code-fg-color);\n  direction: ltr;\n}\n@media print {\n  .md-typeset code,\n.md-typeset pre,\n.md-typeset kbd {\n    white-space: pre-wrap;\n  }\n}\n.md-typeset code {\n  padding: 0 0.2941176471em;\n  font-size: 0.85em;\n  word-break: break-word;\n  background-color: var(--md-code-bg-color);\n  border-radius: 0.1rem;\n  box-decoration-break: clone;\n}\n.md-typeset code:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset h1 code,\n.md-typeset h2 code,\n.md-typeset h3 code,\n.md-typeset h4 code,\n.md-typeset h5 code,\n.md-typeset h6 code {\n  margin: initial;\n  padding: initial;\n  background-color: transparent;\n  box-shadow: none;\n}\n.md-typeset a > code {\n  color: currentColor;\n}\n.md-typeset pre {\n  position: relative;\n  line-height: 1.4;\n}\n.md-typeset pre > code {\n  display: block;\n  margin: 0;\n  padding: 0.7720588235em 1.1764705882em;\n  overflow: auto;\n  word-break: normal;\n  box-shadow: none;\n  box-decoration-break: slice;\n  touch-action: auto;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-typeset pre > code:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-typeset pre > code::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > pre {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > pre code {\n    border-radius: 0;\n  }\n}\n.md-typeset kbd {\n  display: inline-block;\n  padding: 0 0.6666666667em;\n  color: var(--md-default-fg-color);\n  font-size: 0.75em;\n  vertical-align: text-top;\n  word-break: break-word;\n  background-color: var(--md-typeset-kbd-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.1rem 0 0.05rem var(--md-typeset-kbd-border-color), 0 0.1rem 0 var(--md-typeset-kbd-border-color), 0 -0.1rem 0.2rem var(--md-typeset-kbd-accent-color) inset;\n}\n.md-typeset mark {\n  color: inherit;\n  word-break: break-word;\n  background-color: var(--md-typeset-mark-color);\n  box-decoration-break: clone;\n}\n.md-typeset abbr {\n  text-decoration: none;\n  border-bottom: 0.05rem dotted var(--md-default-fg-color--light);\n  cursor: help;\n}\n@media (hover: none) {\n  .md-typeset abbr {\n    position: relative;\n  }\n  .md-typeset abbr[title]:focus::after, .md-typeset abbr[title]:hover::after {\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n    position: absolute;\n    left: 0;\n    display: inline-block;\n    width: auto;\n    min-width: max-content;\n    max-width: 80%;\n    margin-top: 2em;\n    padding: 0.2rem 0.3rem;\n    color: var(--md-default-bg-color);\n    font-size: 0.7rem;\n    background-color: var(--md-default-fg-color);\n    border-radius: 0.1rem;\n    content: attr(title);\n  }\n}\n.md-typeset small {\n  opacity: 0.75;\n}\n.md-typeset sup,\n.md-typeset sub {\n  margin-left: 0.078125em;\n}\n[dir=rtl] .md-typeset sup,\n[dir=rtl] .md-typeset sub {\n  margin-right: 0.078125em;\n  margin-left: initial;\n}\n.md-typeset blockquote {\n  padding-left: 0.6rem;\n  color: var(--md-default-fg-color--light);\n  border-left: 0.2rem solid var(--md-default-fg-color--lighter);\n}\n[dir=rtl] .md-typeset blockquote {\n  padding-right: 0.6rem;\n  padding-left: initial;\n  border-right: 0.2rem solid var(--md-default-fg-color--lighter);\n  border-left: initial;\n}\n.md-typeset ul {\n  list-style-type: disc;\n}\n.md-typeset ul,\n.md-typeset ol {\n  margin-left: 0.625em;\n  padding: 0;\n}\n[dir=rtl] .md-typeset ul,\n[dir=rtl] .md-typeset ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset ul ol,\n.md-typeset ol ol {\n  list-style-type: lower-alpha;\n}\n.md-typeset ul ol ol,\n.md-typeset ol ol ol {\n  list-style-type: lower-roman;\n}\n.md-typeset ul li,\n.md-typeset ol li {\n  margin-bottom: 0.5em;\n  margin-left: 1.25em;\n}\n[dir=rtl] .md-typeset ul li,\n[dir=rtl] .md-typeset ol li {\n  margin-right: 1.25em;\n  margin-left: initial;\n}\n.md-typeset ul li p,\n.md-typeset ul li blockquote,\n.md-typeset ol li p,\n.md-typeset ol li blockquote {\n  margin: 0.5em 0;\n}\n.md-typeset ul li:last-child,\n.md-typeset ol li:last-child {\n  margin-bottom: 0;\n}\n.md-typeset ul li ul,\n.md-typeset ul li ol,\n.md-typeset ol li ul,\n.md-typeset ol li ol {\n  margin: 0.5em 0 0.5em 0.625em;\n}\n[dir=rtl] .md-typeset ul li ul,\n[dir=rtl] .md-typeset ul li ol,\n[dir=rtl] .md-typeset ol li ul,\n[dir=rtl] .md-typeset ol li ol {\n  margin-right: 0.625em;\n  margin-left: initial;\n}\n.md-typeset dd {\n  margin: 1em 0 1.5em 1.875em;\n}\n[dir=rtl] .md-typeset dd {\n  margin-right: 1.875em;\n  margin-left: initial;\n}\n.md-typeset img,\n.md-typeset svg {\n  max-width: 100%;\n  height: auto;\n}\n.md-typeset img[align=left],\n.md-typeset svg[align=left] {\n  margin: 1em;\n  margin-left: 0;\n}\n.md-typeset img[align=right],\n.md-typeset svg[align=right] {\n  margin: 1em;\n  margin-right: 0;\n}\n.md-typeset img[align]:only-child,\n.md-typeset svg[align]:only-child {\n  margin-top: 0;\n}\n.md-typeset figure {\n  width: fit-content;\n  max-width: 100%;\n  margin: 0 auto;\n  text-align: center;\n}\n.md-typeset figure img {\n  display: block;\n}\n.md-typeset figcaption {\n  max-width: 24rem;\n  margin: 1em auto 2em;\n  font-style: italic;\n}\n.md-typeset iframe {\n  max-width: 100%;\n}\n.md-typeset table:not([class]) {\n  display: inline-block;\n  max-width: 100%;\n  overflow: auto;\n  font-size: 0.64rem;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);\n  touch-action: auto;\n}\n@media print {\n  .md-typeset table:not([class]) {\n    display: table;\n  }\n}\n.md-typeset table:not([class]) + * {\n  margin-top: 1.5em;\n}\n.md-typeset table:not([class]) th > *:first-child,\n.md-typeset table:not([class]) td > *:first-child {\n  margin-top: 0;\n}\n.md-typeset table:not([class]) th > *:last-child,\n.md-typeset table:not([class]) td > *:last-child {\n  margin-bottom: 0;\n}\n.md-typeset table:not([class]) th:not([align]),\n.md-typeset table:not([class]) td:not([align]) {\n  text-align: left;\n}\n[dir=rtl] .md-typeset table:not([class]) th:not([align]),\n[dir=rtl] .md-typeset table:not([class]) td:not([align]) {\n  text-align: right;\n}\n.md-typeset table:not([class]) th {\n  min-width: 5rem;\n  padding: 0.9375em 1.25em;\n  color: var(--md-default-bg-color);\n  vertical-align: top;\n  background-color: var(--md-default-fg-color--light);\n}\n.md-typeset table:not([class]) th a {\n  color: inherit;\n}\n.md-typeset table:not([class]) td {\n  padding: 0.9375em 1.25em;\n  vertical-align: top;\n  border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n}\n.md-typeset table:not([class]) tr {\n  transition: background-color 125ms;\n}\n.md-typeset table:not([class]) tr:hover {\n  background-color: rgba(0, 0, 0, 0.035);\n  box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;\n}\n.md-typeset table:not([class]) tr:first-child td {\n  border-top: 0;\n}\n.md-typeset table:not([class]) a {\n  word-break: normal;\n}\n.md-typeset table th[role=columnheader] {\n  cursor: pointer;\n}\n.md-typeset table th[role=columnheader]::after {\n  display: inline-block;\n  width: 1.2em;\n  height: 1.2em;\n  margin-left: 0.5em;\n  vertical-align: sub;\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-typeset table th[role=columnheader][aria-sort=ascending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--ascending);\n}\n.md-typeset table th[role=columnheader][aria-sort=descending]::after {\n  background-color: currentColor;\n  mask-image: var(--md-typeset-table--descending);\n}\n.md-typeset__scrollwrap {\n  margin: 1em -0.8rem;\n  overflow-x: auto;\n  touch-action: auto;\n}\n.md-typeset__table {\n  display: inline-block;\n  margin-bottom: 0.5em;\n  padding: 0 0.8rem;\n}\n@media print {\n  .md-typeset__table {\n    display: block;\n  }\n}\nhtml .md-typeset__table table {\n  display: table;\n  width: 100%;\n  margin: 0;\n  overflow: hidden;\n}\n\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  font-size: 125%;\n}\n@media screen and (min-width: 100em) {\n  html {\n    font-size: 137.5%;\n  }\n}\n@media screen and (min-width: 125em) {\n  html {\n    font-size: 150%;\n  }\n}\n\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  font-size: 0.5rem;\n  background-color: var(--md-default-bg-color);\n}\n@media print {\n  body {\n    display: block;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  body[data-md-state=lock] {\n    position: fixed;\n  }\n}\n\n.md-grid {\n  max-width: 61rem;\n  margin-right: auto;\n  margin-left: auto;\n}\n\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n}\n@media print {\n  .md-container {\n    display: block;\n  }\n}\n\n.md-main {\n  flex-grow: 1;\n}\n.md-main__inner {\n  display: flex;\n  height: 100%;\n  margin-top: 1.5rem;\n}\n\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.md-toggle {\n  display: none;\n}\n\n.md-skip {\n  position: fixed;\n  z-index: -1;\n  margin: 0.5rem;\n  padding: 0.3rem 0.5rem;\n  color: var(--md-default-bg-color);\n  font-size: 0.64rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(0.4rem);\n  opacity: 0;\n}\n.md-skip:focus {\n  z-index: 10;\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 175ms 75ms;\n}\n\n@page {\n  margin: 25mm;\n}\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-announce {\n    display: none;\n  }\n}\n.md-announce__inner {\n  margin: 0.6rem auto;\n  padding: 0 0.8rem;\n  color: var(--md-footer-fg-color);\n  font-size: 0.7rem;\n}\n\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n.md-clipboard {\n  position: absolute;\n  top: 0.5em;\n  right: 0.5em;\n  z-index: 1;\n  width: 1.5em;\n  height: 1.5em;\n  color: var(--md-default-fg-color--lightest);\n  border-radius: 0.1rem;\n  cursor: pointer;\n  transition: color 250ms;\n}\n@media print {\n  .md-clipboard {\n    display: none;\n  }\n}\n.md-clipboard:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n:hover > .md-clipboard {\n  color: var(--md-default-fg-color--light);\n}\n.md-clipboard:focus, .md-clipboard:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-clipboard::after {\n  display: block;\n  width: 1.125em;\n  height: 1.125em;\n  margin: 0 auto;\n  background-color: currentColor;\n  mask-image: var(--md-clipboard-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n.md-clipboard--inline {\n  cursor: pointer;\n}\n.md-clipboard--inline code {\n  transition: color 250ms, background-color 250ms;\n}\n.md-clipboard--inline:focus code, .md-clipboard--inline:hover code {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n\n.md-content {\n  flex-grow: 1;\n  overflow: hidden;\n  scroll-padding-top: 51.2rem;\n}\n.md-content__inner {\n  margin: 0 0.8rem 1.2rem;\n  padding-top: 0.6rem;\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-left: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--primary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n    margin-left: 0.8rem;\n  }\n  .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 1.2rem;\n  }\n  [dir=rtl] .md-sidebar--secondary:not([hidden]) ~ .md-content > .md-content__inner {\n    margin-right: 0.8rem;\n    margin-left: 1.2rem;\n  }\n}\n.md-content__inner::before {\n  display: block;\n  height: 0.4rem;\n  content: \"\";\n}\n.md-content__inner > :last-child {\n  margin-bottom: 0;\n}\n.md-content__button {\n  float: right;\n  margin: 0.4rem 0;\n  margin-left: 0.4rem;\n  padding: 0;\n}\n@media print {\n  .md-content__button {\n    display: none;\n  }\n}\n[dir=rtl] .md-content__button {\n  float: left;\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n[dir=rtl] .md-content__button svg {\n  transform: scaleX(-1);\n}\n.md-typeset .md-content__button {\n  color: var(--md-default-fg-color--lighter);\n}\n.md-content__button svg {\n  display: inline;\n  vertical-align: top;\n}\n\n.md-dialog {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);\n  position: fixed;\n  right: 0.8rem;\n  bottom: 0.8rem;\n  left: initial;\n  z-index: 2;\n  min-width: 11.1rem;\n  padding: 0.4rem 0.6rem;\n  background-color: var(--md-default-fg-color);\n  border-radius: 0.1rem;\n  transform: translateY(100%);\n  opacity: 0;\n  transition: transform 0ms 400ms, opacity 400ms;\n  pointer-events: none;\n}\n@media print {\n  .md-dialog {\n    display: none;\n  }\n}\n[dir=rtl] .md-dialog {\n  right: initial;\n  left: 0.8rem;\n}\n.md-dialog[data-md-state=open] {\n  transform: translateY(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1), opacity 400ms;\n  pointer-events: initial;\n}\n.md-dialog__inner {\n  color: var(--md-default-bg-color);\n  font-size: 0.7rem;\n}\n\n.md-typeset .md-button {\n  display: inline-block;\n  padding: 0.625em 2em;\n  color: var(--md-primary-fg-color);\n  font-weight: 700;\n  border: 0.1rem solid currentColor;\n  border-radius: 0.1rem;\n  transition: color 125ms, background-color 125ms, border-color 125ms;\n}\n.md-typeset .md-button--primary {\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  border-color: var(--md-primary-fg-color);\n}\n.md-typeset .md-button:focus, .md-typeset .md-button:hover {\n  color: var(--md-accent-bg-color);\n  background-color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .md-input {\n  height: 1.8rem;\n  padding: 0 0.6rem;\n  font-size: 0.8rem;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.1);\n  transition: box-shadow 250ms;\n}\n.md-typeset .md-input:focus, .md-typeset .md-input:hover {\n  box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.15), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.15);\n}\n.md-typeset .md-input--stretch {\n  width: 100%;\n}\n\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0), 0 0.2rem 0.4rem rgba(0, 0, 0, 0);\n  transition: color 250ms, background-color 250ms;\n}\n@media print {\n  .md-header {\n    display: none;\n  }\n}\n.md-header[data-md-state=shadow] {\n  box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), color 250ms, background-color 250ms, box-shadow 250ms;\n}\n.md-header[data-md-state=hidden] {\n  transform: translateY(-100%);\n  transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), color 250ms, background-color 250ms, box-shadow 250ms;\n}\n.md-header__inner {\n  display: flex;\n  align-items: center;\n  padding: 0 0.2rem;\n}\n.md-header__button {\n  position: relative;\n  z-index: 1;\n  display: inline-block;\n  margin: 0.2rem;\n  padding: 0.4rem;\n  color: currentColor;\n  vertical-align: middle;\n  cursor: pointer;\n  transition: opacity 250ms;\n}\n.md-header__button:focus, .md-header__button:hover {\n  opacity: 0.7;\n}\n.md-header__button:not(.focus-visible) {\n  outline: none;\n}\n.md-header__button.md-logo {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-header__button.md-logo {\n    display: none;\n  }\n}\n.md-header__button.md-logo img,\n.md-header__button.md-logo svg {\n  display: block;\n  width: 1.2rem;\n  height: 1.2rem;\n  fill: currentColor;\n}\n@media screen and (min-width: 60em) {\n  .md-header__button[for=__search] {\n    display: none;\n  }\n}\n.no-js .md-header__button[for=__search] {\n  display: none;\n}\n[dir=rtl] .md-header__button[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__button[for=__drawer] {\n    display: none;\n  }\n}\n.md-header__topic {\n  position: absolute;\n  display: flex;\n  max-width: 100%;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n}\n.md-header__topic + .md-header__topic {\n  z-index: -1;\n  transform: translateX(1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__topic + .md-header__topic {\n  transform: translateX(-1.25rem);\n}\n.md-header__title {\n  flex-grow: 1;\n  height: 2.4rem;\n  margin-right: 0.4rem;\n  margin-left: 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-header__title[data-md-state=active] .md-header__topic {\n  z-index: -1;\n  transform: translateX(-1.25rem);\n  opacity: 0;\n  transition: transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-header__title[data-md-state=active] .md-header__topic {\n  transform: translateX(1.25rem);\n}\n.md-header__title[data-md-state=active] .md-header__topic + .md-header__topic {\n  z-index: 0;\n  transform: translateX(0);\n  opacity: 1;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: initial;\n}\n.md-header__title > .md-header__ellipsis {\n  position: relative;\n  width: 100%;\n  height: 100%;\n}\n.md-header__options {\n  display: flex;\n  flex-shrink: 0;\n  max-width: 100%;\n  white-space: nowrap;\n  transition: max-width 0ms 250ms, opacity 250ms 250ms;\n}\n.md-header__options > [data-md-state=hidden] {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-header__options {\n  max-width: 0;\n  opacity: 0;\n  transition: max-width 0ms, opacity 0ms;\n}\n.md-header__source {\n  display: none;\n}\n@media screen and (min-width: 60em) {\n  .md-header__source {\n    display: block;\n    width: 11.7rem;\n    max-width: 11.7rem;\n    margin-left: 1rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1rem;\n    margin-left: initial;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-header__source {\n    margin-left: 1.4rem;\n  }\n  [dir=rtl] .md-header__source {\n    margin-right: 1.4rem;\n  }\n}\n\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n}\n@media print {\n  .md-footer {\n    display: none;\n  }\n}\n.md-footer__inner {\n  padding: 0.2rem;\n  overflow: auto;\n}\n.md-footer__link {\n  display: flex;\n  padding-top: 1.4rem;\n  padding-bottom: 0.4rem;\n  transition: opacity 250ms;\n}\n@media screen and (min-width: 45em) {\n  .md-footer__link {\n    width: 50%;\n  }\n}\n.md-footer__link:focus, .md-footer__link:hover {\n  opacity: 0.7;\n}\n.md-footer__link--prev {\n  float: left;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--prev {\n    width: 25%;\n  }\n  .md-footer__link--prev .md-footer__title {\n    display: none;\n  }\n}\n[dir=rtl] .md-footer__link--prev {\n  float: right;\n}\n[dir=rtl] .md-footer__link--prev svg {\n  transform: scaleX(-1);\n}\n.md-footer__link--next {\n  float: right;\n  text-align: right;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-footer__link--next {\n    width: 75%;\n  }\n}\n[dir=rtl] .md-footer__link--next {\n  float: left;\n  text-align: left;\n}\n[dir=rtl] .md-footer__link--next svg {\n  transform: scaleX(-1);\n}\n.md-footer__title {\n  position: relative;\n  flex-grow: 1;\n  max-width: calc(100% - 2.4rem);\n  padding: 0 1rem;\n  font-size: 0.9rem;\n  line-height: 2.4rem;\n}\n.md-footer__button {\n  margin: 0.2rem;\n  padding: 0.4rem;\n}\n.md-footer__direction {\n  position: absolute;\n  right: 0;\n  left: 0;\n  margin-top: -1rem;\n  padding: 0 1rem;\n  font-size: 0.64rem;\n  opacity: 0.7;\n}\n\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n}\n.md-footer-meta__inner {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  padding: 0.2rem;\n}\nhtml .md-footer-meta.md-typeset a {\n  color: var(--md-footer-fg-color--light);\n}\nhtml .md-footer-meta.md-typeset a:focus, html .md-footer-meta.md-typeset a:hover {\n  color: var(--md-footer-fg-color);\n}\n\n.md-footer-copyright {\n  width: 100%;\n  margin: auto 0.6rem;\n  padding: 0.4rem 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: 0.64rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-copyright {\n    width: auto;\n  }\n}\n.md-footer-copyright__highlight {\n  color: var(--md-footer-fg-color--light);\n}\n\n.md-footer-social {\n  margin: 0 0.4rem;\n  padding: 0.2rem 0 0.6rem;\n}\n@media screen and (min-width: 45em) {\n  .md-footer-social {\n    padding: 0.6rem 0;\n  }\n}\n.md-footer-social__link {\n  display: inline-block;\n  width: 1.6rem;\n  height: 1.6rem;\n  text-align: center;\n}\n.md-footer-social__link::before {\n  line-height: 1.9;\n}\n.md-footer-social__link svg {\n  max-height: 0.8rem;\n  vertical-align: -25%;\n  fill: currentColor;\n}\n\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n.md-nav {\n  font-size: 0.7rem;\n  line-height: 1.3;\n}\n.md-nav__title {\n  display: block;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n}\n.md-nav__title .md-nav__button {\n  display: none;\n}\n.md-nav__title .md-nav__button img {\n  width: auto;\n  height: 100%;\n}\n.md-nav__title .md-nav__button.md-logo img,\n.md-nav__title .md-nav__button.md-logo svg {\n  display: block;\n  width: 2.4rem;\n  height: 2.4rem;\n  fill: currentColor;\n}\n.md-nav__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-nav__item {\n  padding: 0 0.6rem;\n}\n.md-nav__item .md-nav__item {\n  padding-right: 0;\n}\n[dir=rtl] .md-nav__item .md-nav__item {\n  padding-right: 0.6rem;\n  padding-left: 0;\n}\n.md-nav__link {\n  display: block;\n  margin-top: 0.625em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  cursor: pointer;\n  transition: color 125ms;\n  scroll-snap-align: start;\n}\n.md-nav__link[data-md-state=blur] {\n  color: var(--md-default-fg-color--light);\n}\n.md-nav__item .md-nav__link--active {\n  color: var(--md-typeset-a-color);\n}\n.md-nav__item--nested > .md-nav__link {\n  color: inherit;\n}\n.md-nav__link:focus, .md-nav__link:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-nav--primary .md-nav__link[for=__toc] {\n  display: none;\n}\n.md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n  display: block;\n  width: 100%;\n  height: 100%;\n  mask-image: var(--md-toc-icon);\n  background-color: currentColor;\n}\n.md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n  display: none;\n}\n.md-nav__source {\n  display: none;\n}\n@media screen and (max-width: 76.1875em) {\n  .md-nav--primary, .md-nav--primary .md-nav {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: 1;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n  }\n  .md-nav--primary .md-nav__title,\n.md-nav--primary .md-nav__item {\n    font-size: 0.8rem;\n    line-height: 1.5;\n  }\n  .md-nav--primary .md-nav__title {\n    position: relative;\n    height: 5.6rem;\n    padding: 3rem 0.8rem 0.2rem;\n    color: var(--md-default-fg-color--light);\n    font-weight: 400;\n    line-height: 2.4rem;\n    white-space: nowrap;\n    background-color: var(--md-default-fg-color--lightest);\n    cursor: pointer;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    position: absolute;\n    top: 0.4rem;\n    left: 0.4rem;\n    display: block;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin: 0.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon {\n    right: 0.4rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--prev);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list {\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0.05rem 0 var(--md-default-fg-color--lightest) inset;\n    scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n  }\n  .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child {\n    border-top: 0;\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color);\n  }\n  .md-nav--primary .md-nav__title .md-logo {\n    position: absolute;\n    top: 0.2rem;\n    left: 0.2rem;\n    display: block;\n    margin: 0.2rem;\n    padding: 0.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__title .md-logo {\n    right: 0.2rem;\n    left: initial;\n  }\n  .md-nav--primary .md-nav__list {\n    flex: 1;\n  }\n  .md-nav--primary .md-nav__item {\n    padding: 0;\n    border-top: 0.05rem solid var(--md-default-fg-color--lightest);\n  }\n  .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__item--nested > .md-nav__link {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link {\n    color: var(--md-typeset-a-color);\n  }\n  .md-nav--primary .md-nav__item--active > .md-nav__link:focus, .md-nav--primary .md-nav__item--active > .md-nav__link:hover {\n    color: var(--md-accent-fg-color);\n  }\n  .md-nav--primary .md-nav__link {\n    position: relative;\n    margin-top: 0;\n    padding: 0.6rem 0.8rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon {\n    position: absolute;\n    top: 50%;\n    right: 0.6rem;\n    width: 1.2rem;\n    height: 1.2rem;\n    margin-top: -0.6rem;\n    color: inherit;\n    font-size: 1.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon {\n    right: initial;\n    left: 0.6rem;\n  }\n  .md-nav--primary .md-nav__link .md-nav__icon::after {\n    display: block;\n    width: 100%;\n    height: 100%;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  [dir=rtl] .md-nav--primary .md-nav__icon::after {\n    transform: scale(-1);\n  }\n  .md-nav--primary .md-nav--secondary .md-nav__link {\n    position: static;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav {\n    position: static;\n    background-color: transparent;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-left: 1.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {\n    padding-right: 1.4rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-left: 2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {\n    padding-right: 2rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 2.6rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 2.6rem;\n    padding-left: initial;\n  }\n  .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-left: 3.2rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link {\n    padding-right: 3.2rem;\n    padding-left: initial;\n  }\n  .md-nav--secondary {\n    background-color: transparent;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: flex;\n    transform: translateX(100%);\n    opacity: 0;\n    transition: transform 250ms cubic-bezier(0.8, 0, 0.6, 1), opacity 125ms 50ms;\n  }\n  [dir=rtl] .md-nav__toggle ~ .md-nav {\n    transform: translateX(-100%);\n  }\n  .md-nav__toggle:checked ~ .md-nav {\n    transform: translateX(0);\n    opacity: 1;\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 125ms 125ms;\n  }\n  .md-nav__toggle:checked ~ .md-nav > .md-nav__list {\n    backface-visibility: hidden;\n  }\n}\n@media screen and (max-width: 59.9375em) {\n  .md-nav--primary .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n  }\n  [dir=rtl] .md-nav--primary .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--primary .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--primary .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n  .md-nav__source {\n    display: block;\n    padding: 0 0.2rem;\n    color: var(--md-primary-bg-color);\n    background-color: var(--md-primary-fg-color--dark);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-nav--integrated .md-nav__link[for=__toc] {\n    display: block;\n    padding-right: 2.4rem;\n    scroll-snap-align: initial;\n  }\n  [dir=rtl] .md-nav--integrated .md-nav__link[for=__toc] {\n    padding-right: 0.8rem;\n    padding-left: 2.4rem;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] .md-icon::after {\n    content: \"\";\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] + .md-nav__link {\n    display: none;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: flex;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-nav--secondary .md-nav__title[for=__toc] {\n    scroll-snap-align: start;\n  }\n  .md-nav--secondary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-nav {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n  }\n  .md-nav--primary .md-nav__title[for=__drawer] {\n    scroll-snap-align: start;\n  }\n  .md-nav--primary .md-nav__title .md-nav__icon {\n    display: none;\n  }\n  .md-nav__toggle ~ .md-nav {\n    display: none;\n  }\n  .md-nav__toggle:checked ~ .md-nav, .md-nav__toggle:indeterminate ~ .md-nav {\n    display: block;\n  }\n  .md-nav__item--nested > .md-nav > .md-nav__title {\n    display: none;\n  }\n  .md-nav__item--section {\n    display: block;\n    margin: 1.25em 0;\n  }\n  .md-nav__item--section:last-child {\n    margin-bottom: 0;\n  }\n  .md-nav__item--section > .md-nav__link {\n    display: none;\n  }\n  .md-nav__item--section > .md-nav {\n    display: block;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav__item--section > .md-nav > .md-nav__list > .md-nav__item {\n    padding: 0;\n  }\n  .md-nav__icon {\n    float: right;\n    width: 0.9rem;\n    height: 0.9rem;\n    transition: transform 250ms;\n  }\n  [dir=rtl] .md-nav__icon {\n    float: left;\n    transform: rotate(180deg);\n  }\n  .md-nav__icon::after {\n    display: inline-block;\n    width: 100%;\n    height: 100%;\n    vertical-align: -0.1rem;\n    background-color: currentColor;\n    mask-image: var(--md-nav-icon--next);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n  .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon, .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link .md-nav__icon {\n    transform: rotate(90deg);\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--nested,\n.md-nav--lifted > .md-nav__title {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active {\n    display: block;\n    padding: 0;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {\n    display: none;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav > .md-nav__title {\n    display: block;\n    padding: 0 0.6rem;\n    pointer-events: none;\n    scroll-snap-align: start;\n  }\n  .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav__item {\n    padding-right: 0.6rem;\n  }\n  .md-nav--lifted .md-nav[data-md-level=\"1\"] {\n    display: block;\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav {\n    display: block;\n    margin-bottom: 1.25em;\n    border-left: 0.05rem solid var(--md-primary-fg-color);\n  }\n  .md-nav--integrated .md-nav__link[for=__toc] ~ .md-nav > .md-nav__title {\n    display: none;\n  }\n}\n\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n.md-search {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search {\n    padding: 0.2rem 0;\n  }\n}\n.no-js .md-search {\n  display: none;\n}\n.md-search__overlay {\n  z-index: 1;\n  opacity: 0;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__overlay {\n    position: absolute;\n    top: 0.2rem;\n    left: -2.2rem;\n    width: 2rem;\n    height: 2rem;\n    overflow: hidden;\n    background-color: var(--md-default-bg-color);\n    border-radius: 1rem;\n    transform-origin: center;\n    transition: transform 300ms 100ms, opacity 200ms 200ms;\n    pointer-events: none;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: -2.2rem;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    opacity: 1;\n    transition: transform 400ms, opacity 100ms;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    cursor: pointer;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [dir=rtl] .md-search__overlay {\n    right: 0;\n    left: initial;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    width: 100%;\n    height: 200vh;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@media screen and (max-width: 29.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(45);\n  }\n}\n@media screen and (min-width: 30em) and (max-width: 44.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(60);\n  }\n}\n@media screen and (min-width: 45em) and (max-width: 59.9375em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__overlay {\n    transform: scale(75);\n  }\n}\n.md-search__inner {\n  backface-visibility: hidden;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__inner {\n    position: fixed;\n    top: 0;\n    left: 100%;\n    z-index: 2;\n    width: 100%;\n    height: 100%;\n    transform: translateX(5%);\n    opacity: 0;\n    transition: right 0ms 300ms, left 0ms 300ms, transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms 150ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    left: 0;\n    transform: translateX(0);\n    opacity: 1;\n    transition: right 0ms 0ms, left 0ms 0ms, transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms 150ms;\n  }\n  [dir=rtl] [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    right: 0;\n    left: initial;\n  }\n  html [dir=rtl] .md-search__inner {\n    right: 100%;\n    left: initial;\n    transform: translateX(-5%);\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__inner {\n    position: relative;\n    float: right;\n    width: 11.7rem;\n    padding: 0.1rem 0;\n    transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n  }\n  [dir=rtl] .md-search__inner {\n    float: left;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-toggle=search]:checked ~ .md-header .md-search__inner {\n    width: 34.4rem;\n  }\n}\n.md-search__form {\n  position: relative;\n}\n@media screen and (min-width: 60em) {\n  .md-search__form {\n    border-radius: 0.1rem;\n  }\n}\n.md-search__input {\n  position: relative;\n  z-index: 2;\n  padding: 0 2.2rem 0 3.6rem;\n  text-overflow: ellipsis;\n  background-color: var(--md-default-bg-color);\n  box-shadow: 0 0 0.6rem transparent;\n  transition: color 250ms, background-color 250ms, box-shadow 250ms;\n}\n[dir=rtl] .md-search__input {\n  padding: 0 3.6rem 0 2.2rem;\n}\n.md-search__input::placeholder {\n  transition: color 250ms;\n}\n.md-search__input ~ .md-search__icon, .md-search__input::placeholder {\n  color: var(--md-default-fg-color--light);\n}\n.md-search__input::-ms-clear {\n  display: none;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input {\n  box-shadow: 0 0 0.6rem rgba(0, 0, 0, 0.07);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__input {\n    width: 100%;\n    height: 2.4rem;\n    font-size: 0.9rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__input {\n    width: 100%;\n    height: 1.8rem;\n    padding-left: 2.2rem;\n    color: inherit;\n    font-size: 0.8rem;\n    background-color: rgba(0, 0, 0, 0.26);\n    border-radius: 0.1rem;\n  }\n  [dir=rtl] .md-search__input {\n    padding-right: 2.2rem;\n  }\n  .md-search__input + .md-search__icon {\n    color: var(--md-primary-bg-color);\n  }\n  .md-search__input::placeholder {\n    color: var(--md-primary-bg-color--light);\n  }\n  .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input {\n    color: var(--md-default-fg-color);\n    text-overflow: clip;\n    background-color: var(--md-default-bg-color);\n    border-radius: 0.1rem 0.1rem 0 0;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle=search]:checked ~ .md-header .md-search__input::placeholder {\n    color: var(--md-default-fg-color--light);\n  }\n}\n.md-search__icon {\n  position: absolute;\n  z-index: 2;\n  width: 1.2rem;\n  height: 1.2rem;\n  cursor: pointer;\n  transition: color 250ms, opacity 250ms;\n}\n.md-search__icon:hover {\n  opacity: 0.7;\n}\n.md-search__icon[for=__search] {\n  top: 0.3rem;\n  left: 0.5rem;\n}\n[dir=rtl] .md-search__icon[for=__search] {\n  right: 0.5rem;\n  left: initial;\n}\n[dir=rtl] .md-search__icon[for=__search] svg {\n  transform: scaleX(-1);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[for=__search] {\n    top: 0.6rem;\n    left: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[for=__search] {\n    right: 0.8rem;\n    left: initial;\n  }\n  .md-search__icon[for=__search] svg:first-child {\n    display: none;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__icon[for=__search] {\n    pointer-events: none;\n  }\n  .md-search__icon[for=__search] svg:last-child {\n    display: none;\n  }\n}\n.md-search__icon[type=reset] {\n  top: 0.3rem;\n  right: 0.5rem;\n  transform: scale(0.75);\n  opacity: 0;\n  transition: transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 150ms;\n  pointer-events: none;\n}\n[dir=rtl] .md-search__icon[type=reset] {\n  right: initial;\n  left: 0.5rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__icon[type=reset] {\n    top: 0.6rem;\n    right: 0.8rem;\n  }\n  [dir=rtl] .md-search__icon[type=reset] {\n    right: initial;\n    left: 0.8rem;\n  }\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset] {\n  transform: scale(1);\n  opacity: 1;\n  pointer-events: initial;\n}\n[data-md-toggle=search]:checked ~ .md-header .md-search__input:valid ~ .md-search__icon[type=reset]:hover {\n  opacity: 0.7;\n}\n.md-search__output {\n  position: absolute;\n  z-index: 1;\n  width: 100%;\n  overflow: hidden;\n  border-radius: 0 0 0.1rem 0.1rem;\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search__output {\n    top: 2.4rem;\n    bottom: 0;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__output {\n    top: 1.9rem;\n    opacity: 0;\n    transition: opacity 400ms;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__output {\n    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.4);\n    opacity: 1;\n  }\n}\n.md-search__scrollwrap {\n  height: 100%;\n  overflow-y: auto;\n  background-color: var(--md-default-bg-color);\n  backface-visibility: hidden;\n  touch-action: pan-y;\n}\n@media (max-resolution: 1dppx) {\n  .md-search__scrollwrap {\n    transform: translateZ(0);\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search__scrollwrap {\n    width: 23.4rem;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-search__scrollwrap {\n    width: 34.4rem;\n  }\n}\n@media screen and (min-width: 60em) {\n  .md-search__scrollwrap {\n    max-height: 0;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n  }\n  [data-md-toggle=search]:checked ~ .md-header .md-search__scrollwrap {\n    max-height: 75vh;\n  }\n  .md-search__scrollwrap:hover {\n    scrollbar-color: var(--md-accent-fg-color) transparent;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar {\n    width: 0.2rem;\n    height: 0.2rem;\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb {\n    background-color: var(--md-default-fg-color--lighter);\n  }\n  .md-search__scrollwrap::-webkit-scrollbar-thumb:hover {\n    background-color: var(--md-accent-fg-color);\n  }\n}\n\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n}\n.md-search-result__meta {\n  padding: 0 0.8rem;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.8rem;\n  background-color: var(--md-default-fg-color--lightest);\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__meta {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__meta {\n    padding-right: 2.2rem;\n    padding-left: initial;\n  }\n}\n.md-search-result__list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.md-search-result__item {\n  box-shadow: 0 -0.05rem 0 var(--md-default-fg-color--lightest);\n}\n.md-search-result__item:first-child {\n  box-shadow: none;\n}\n.md-search-result__link {\n  display: block;\n  outline: none;\n  transition: background-color 250ms;\n  scroll-snap-align: start;\n}\n.md-search-result__link:focus, .md-search-result__link:hover {\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__link:last-child p:last-child {\n  margin-bottom: 0.6rem;\n}\n.md-search-result__more summary {\n  display: block;\n  padding: 0.75em 0.8rem;\n  color: var(--md-typeset-a-color);\n  font-size: 0.64rem;\n  outline: 0;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__more summary {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__more summary {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__more summary:focus, .md-search-result__more summary:hover {\n  color: var(--md-accent-fg-color);\n  background-color: var(--md-accent-fg-color--transparent);\n}\n.md-search-result__more summary::marker, .md-search-result__more summary::-webkit-details-marker {\n  display: none;\n}\n.md-search-result__more summary ~ * > * {\n  opacity: 0.65;\n}\n.md-search-result__article {\n  position: relative;\n  padding: 0 0.8rem;\n  overflow: hidden;\n}\n@media screen and (min-width: 60em) {\n  .md-search-result__article {\n    padding-left: 2.2rem;\n  }\n  [dir=rtl] .md-search-result__article {\n    padding-right: 2.2rem;\n    padding-left: 0.8rem;\n  }\n}\n.md-search-result__article--document .md-search-result__title {\n  margin: 0.55rem 0;\n  font-weight: 400;\n  font-size: 0.8rem;\n  line-height: 1.4;\n}\n.md-search-result__icon {\n  position: absolute;\n  left: 0;\n  width: 1.2rem;\n  height: 1.2rem;\n  margin: 0.5rem;\n  color: var(--md-default-fg-color--light);\n}\n@media screen and (max-width: 59.9375em) {\n  .md-search-result__icon {\n    display: none;\n  }\n}\n.md-search-result__icon::after {\n  display: inline-block;\n  width: 100%;\n  height: 100%;\n  background-color: currentColor;\n  mask-image: var(--md-search-result-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-search-result__icon {\n  right: 0;\n  left: initial;\n}\n[dir=rtl] .md-search-result__icon::after {\n  transform: scaleX(-1);\n}\n.md-search-result__title {\n  margin: 0.5em 0;\n  font-weight: 700;\n  font-size: 0.64rem;\n  line-height: 1.6;\n}\n.md-search-result__teaser {\n  display: -webkit-box;\n  max-height: 2rem;\n  margin: 0.5em 0;\n  overflow: hidden;\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n  line-height: 1.6;\n  text-overflow: ellipsis;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n@media screen and (min-width: 60em) and (max-width: 76.1875em) {\n  .md-search-result__teaser {\n    max-height: 3rem;\n    -webkit-line-clamp: 3;\n  }\n}\n.md-search-result__teaser mark {\n  text-decoration: underline;\n  background-color: transparent;\n}\n.md-search-result__terms {\n  margin: 0.5em 0;\n  font-size: 0.64rem;\n  font-style: italic;\n}\n.md-search-result mark {\n  color: var(--md-accent-fg-color);\n  background-color: transparent;\n}\n\n.md-select {\n  position: relative;\n  z-index: 1;\n}\n.md-select__inner {\n  position: absolute;\n  top: calc(100% - 0.2rem);\n  left: 50%;\n  max-height: 0;\n  margin-top: 0.2rem;\n  color: var(--md-default-fg-color);\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  transform: translate3d(-50%, 0.3rem, 0);\n  opacity: 0;\n  transition: transform 250ms 375ms, opacity 250ms 250ms, max-height 0ms 500ms;\n}\n.md-select:focus-within .md-select__inner, .md-select:hover .md-select__inner {\n  max-height: 10rem;\n  transform: translate3d(-50%, 0, 0);\n  opacity: 1;\n  transition: transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms, max-height 250ms;\n}\n.md-select__inner::after {\n  position: absolute;\n  top: 0;\n  left: 50%;\n  width: 0;\n  height: 0;\n  margin-top: -0.2rem;\n  margin-left: -0.2rem;\n  border: 0.2rem solid transparent;\n  border-top: 0;\n  border-bottom-color: var(--md-default-bg-color);\n  content: \"\";\n}\n.md-select__list {\n  max-height: inherit;\n  margin: 0;\n  padding: 0;\n  overflow: auto;\n  font-size: 0.8rem;\n  list-style-type: none;\n  border-radius: 0.1rem;\n}\n.md-select__item {\n  line-height: 1.8rem;\n}\n.md-select__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  cursor: pointer;\n  transition: background-color 250ms, color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-select__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-select__link:focus, .md-select__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n.md-sidebar {\n  position: sticky;\n  top: 2.4rem;\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: 12.1rem;\n  padding: 1.2rem 0;\n}\n@media print {\n  .md-sidebar {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-sidebar--primary {\n    position: fixed;\n    top: 0;\n    left: -12.1rem;\n    z-index: 3;\n    display: block;\n    width: 12.1rem;\n    height: 100%;\n    background-color: var(--md-default-bg-color);\n    transform: translateX(0);\n    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms;\n  }\n  [dir=rtl] .md-sidebar--primary {\n    right: -12.1rem;\n    left: initial;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.4);\n    transform: translateX(12.1rem);\n  }\n  [dir=rtl] [data-md-toggle=drawer]:checked ~ .md-container .md-sidebar--primary {\n    transform: translateX(-12.1rem);\n  }\n  .md-sidebar--primary .md-sidebar__scrollwrap {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    margin: 0;\n    scroll-snap-type: none;\n    overflow: hidden;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  .md-sidebar {\n    height: 0;\n  }\n  .no-js .md-sidebar {\n    height: auto;\n  }\n}\n.md-sidebar--secondary {\n  display: none;\n  order: 2;\n}\n@media screen and (min-width: 60em) {\n  .md-sidebar--secondary {\n    height: 0;\n  }\n  .no-js .md-sidebar--secondary {\n    height: auto;\n  }\n  .md-sidebar--secondary:not([hidden]) {\n    display: block;\n  }\n  .md-sidebar--secondary .md-sidebar__scrollwrap {\n    touch-action: pan-y;\n  }\n}\n.md-sidebar__scrollwrap {\n  margin: 0 0.2rem;\n  overflow-y: auto;\n  backface-visibility: hidden;\n  scrollbar-width: thin;\n  scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n}\n.md-sidebar__scrollwrap:hover {\n  scrollbar-color: var(--md-accent-fg-color) transparent;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar {\n  width: 0.2rem;\n  height: 0.2rem;\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {\n  background-color: var(--md-default-fg-color--lighter);\n}\n.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {\n  background-color: var(--md-accent-fg-color);\n}\n\n@media screen and (max-width: 76.1875em) {\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n    opacity: 0;\n    transition: width 0ms 250ms, height 0ms 250ms, opacity 250ms;\n  }\n  [data-md-toggle=drawer]:checked ~ .md-overlay {\n    width: 100%;\n    height: 100%;\n    opacity: 1;\n    transition: width 0ms, height 0ms, opacity 250ms;\n  }\n}\n@keyframes md-source__facts--done {\n  0% {\n    height: 0;\n  }\n  100% {\n    height: 0.65rem;\n  }\n}\n@keyframes md-source__fact--done {\n  0% {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n  50% {\n    opacity: 0;\n  }\n  100% {\n    transform: translateY(0%);\n    opacity: 1;\n  }\n}\n.md-source {\n  display: block;\n  font-size: 0.65rem;\n  line-height: 1.2;\n  white-space: nowrap;\n  backface-visibility: hidden;\n  transition: opacity 250ms;\n}\n.md-source:focus, .md-source:hover {\n  opacity: 0.7;\n}\n.md-source__icon {\n  display: inline-block;\n  width: 2.4rem;\n  height: 2.4rem;\n  vertical-align: middle;\n}\n.md-source__icon svg {\n  margin-top: 0.6rem;\n  margin-left: 0.6rem;\n}\n[dir=rtl] .md-source__icon svg {\n  margin-right: 0.6rem;\n  margin-left: initial;\n}\n.md-source__icon + .md-source__repository {\n  margin-left: -2rem;\n  padding-left: 2rem;\n}\n[dir=rtl] .md-source__icon + .md-source__repository {\n  margin-right: -2rem;\n  margin-left: initial;\n  padding-right: 2rem;\n  padding-left: initial;\n}\n.md-source__repository {\n  display: inline-block;\n  max-width: calc(100% - 1.2rem);\n  margin-left: 0.6rem;\n  overflow: hidden;\n  font-weight: 700;\n  text-overflow: ellipsis;\n  vertical-align: middle;\n}\n.md-source__facts {\n  margin: 0;\n  padding: 0;\n  overflow: hidden;\n  font-weight: 700;\n  font-size: 0.55rem;\n  list-style-type: none;\n  opacity: 0.75;\n}\n[data-md-state=done] .md-source__facts {\n  animation: md-source__facts--done 250ms ease-in;\n}\n.md-source__fact {\n  float: left;\n}\n[dir=rtl] .md-source__fact {\n  float: right;\n}\n[data-md-state=done] .md-source__fact {\n  animation: md-source__fact--done 400ms ease-out;\n}\n.md-source__fact::before {\n  margin: 0 0.1rem;\n  content: \"·\";\n}\n.md-source__fact:first-child::before {\n  display: none;\n}\n\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  transition: background-color 250ms;\n}\n@media print {\n  .md-tabs {\n    display: none;\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  .md-tabs {\n    display: none;\n  }\n}\n.md-tabs[data-md-state=hidden] {\n  pointer-events: none;\n}\n.md-tabs__list {\n  margin: 0;\n  margin-left: 0.2rem;\n  padding: 0;\n  white-space: nowrap;\n  list-style: none;\n  contain: content;\n}\n[dir=rtl] .md-tabs__list {\n  margin-right: 0.2rem;\n  margin-left: initial;\n}\n.md-tabs__item {\n  display: inline-block;\n  height: 2.4rem;\n  padding-right: 0.6rem;\n  padding-left: 0.6rem;\n}\n.md-tabs__link {\n  display: block;\n  margin-top: 0.8rem;\n  font-size: 0.7rem;\n  backface-visibility: hidden;\n  opacity: 0.7;\n  transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1), opacity 250ms;\n}\n.md-tabs__link--active, .md-tabs__link:focus, .md-tabs__link:hover {\n  color: inherit;\n  opacity: 1;\n}\n.md-tabs__item:nth-child(2) .md-tabs__link {\n  transition-delay: 20ms;\n}\n.md-tabs__item:nth-child(3) .md-tabs__link {\n  transition-delay: 40ms;\n}\n.md-tabs__item:nth-child(4) .md-tabs__link {\n  transition-delay: 60ms;\n}\n.md-tabs__item:nth-child(5) .md-tabs__link {\n  transition-delay: 80ms;\n}\n.md-tabs__item:nth-child(6) .md-tabs__link {\n  transition-delay: 100ms;\n}\n.md-tabs__item:nth-child(7) .md-tabs__link {\n  transition-delay: 120ms;\n}\n.md-tabs__item:nth-child(8) .md-tabs__link {\n  transition-delay: 140ms;\n}\n.md-tabs__item:nth-child(9) .md-tabs__link {\n  transition-delay: 160ms;\n}\n.md-tabs__item:nth-child(10) .md-tabs__link {\n  transition-delay: 180ms;\n}\n.md-tabs__item:nth-child(11) .md-tabs__link {\n  transition-delay: 200ms;\n}\n.md-tabs__item:nth-child(12) .md-tabs__link {\n  transition-delay: 220ms;\n}\n.md-tabs__item:nth-child(13) .md-tabs__link {\n  transition-delay: 240ms;\n}\n.md-tabs__item:nth-child(14) .md-tabs__link {\n  transition-delay: 260ms;\n}\n.md-tabs__item:nth-child(15) .md-tabs__link {\n  transition-delay: 280ms;\n}\n.md-tabs__item:nth-child(16) .md-tabs__link {\n  transition-delay: 300ms;\n}\n.md-tabs[data-md-state=hidden] .md-tabs__link {\n  transform: translateY(50%);\n  opacity: 0;\n  transition: transform 0ms 100ms, opacity 100ms;\n}\n\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n.md-version {\n  flex-shrink: 0;\n  height: 2.4rem;\n  font-size: 0.8rem;\n}\n.md-version__current {\n  position: relative;\n  top: 0.05rem;\n  margin-right: 0.4rem;\n  margin-left: 1.4rem;\n}\n[dir=rtl] .md-version__current {\n  margin-right: 1.4rem;\n  margin-left: 0.4rem;\n}\n.md-version__current::after {\n  display: inline-block;\n  width: 0.4rem;\n  height: 0.6rem;\n  margin-left: 0.4rem;\n  background-color: currentColor;\n  mask-image: var(--md-version-icon);\n  mask-repeat: no-repeat;\n  content: \"\";\n}\n[dir=rtl] .md-version__current::after {\n  margin-right: 0.4rem;\n  margin-left: initial;\n}\n.md-version__list {\n  position: absolute;\n  top: 0.15rem;\n  z-index: 1;\n  max-height: 1.8rem;\n  margin: 0.2rem 0.8rem;\n  padding: 0;\n  overflow: auto;\n  color: var(--md-default-fg-color);\n  list-style-type: none;\n  background-color: var(--md-default-bg-color);\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.1), 0 0 0.05rem rgba(0, 0, 0, 0.25);\n  opacity: 0;\n  transition: max-height 0ms 500ms, opacity 250ms 250ms;\n  scroll-snap-type: y mandatory;\n}\n.md-version__list:focus-within, .md-version__list:hover {\n  max-height: 10rem;\n  opacity: 1;\n  transition: max-height 250ms, opacity 250ms;\n}\n.md-version__item {\n  line-height: 1.8rem;\n}\n.md-version__link {\n  display: block;\n  width: 100%;\n  padding-right: 1.2rem;\n  padding-left: 0.6rem;\n  white-space: nowrap;\n  cursor: pointer;\n  transition: color 250ms, background-color 250ms;\n  scroll-snap-align: start;\n}\n[dir=rtl] .md-version__link {\n  padding-right: 0.6rem;\n  padding-left: 1.2rem;\n}\n.md-version__link:focus, .md-version__link:hover {\n  background-color: var(--md-default-fg-color--lightest);\n}\n\n:root {\n  --md-admonition-icon--note:\n    svg-load(\"material/pencil.svg\");\n  --md-admonition-icon--abstract:\n    svg-load(\"material/text-subject.svg\");\n  --md-admonition-icon--info:\n    svg-load(\"material/information.svg\");\n  --md-admonition-icon--tip:\n    svg-load(\"material/fire.svg\");\n  --md-admonition-icon--success:\n    svg-load(\"material/check-circle.svg\");\n  --md-admonition-icon--question:\n    svg-load(\"material/help-circle.svg\");\n  --md-admonition-icon--warning:\n    svg-load(\"material/alert.svg\");\n  --md-admonition-icon--failure:\n    svg-load(\"material/close-circle.svg\");\n  --md-admonition-icon--danger:\n    svg-load(\"material/flash-circle.svg\");\n  --md-admonition-icon--bug:\n    svg-load(\"material/bug.svg\");\n  --md-admonition-icon--example:\n    svg-load(\"material/format-list-numbered.svg\");\n  --md-admonition-icon--quote:\n    svg-load(\"material/format-quote-close.svg\");\n}\n\n.md-typeset .admonition, .md-typeset details {\n  margin: 1.5625em 0;\n  padding: 0 0.6rem;\n  overflow: hidden;\n  color: var(--md-admonition-fg-color);\n  font-size: 0.64rem;\n  page-break-inside: avoid;\n  background-color: var(--md-admonition-bg-color);\n  border-left: 0.2rem solid #448aff;\n  border-radius: 0.1rem;\n  box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0.025rem 0.05rem rgba(0, 0, 0, 0.05);\n}\n@media print {\n  .md-typeset .admonition, .md-typeset details {\n    box-shadow: none;\n  }\n}\n[dir=rtl] .md-typeset .admonition, [dir=rtl] .md-typeset details {\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\n.md-typeset .admonition .admonition, .md-typeset details .admonition, .md-typeset .admonition details, .md-typeset details details {\n  margin: 1em 0;\n}\n.md-typeset .admonition .md-typeset__scrollwrap, .md-typeset details .md-typeset__scrollwrap {\n  margin: 1em -0.6rem;\n}\n.md-typeset .admonition .md-typeset__table, .md-typeset details .md-typeset__table {\n  padding: 0 0.6rem;\n}\n.md-typeset .admonition > .tabbed-set:only-child, .md-typeset details > .tabbed-set:only-child {\n  margin-top: 0;\n}\nhtml .md-typeset .admonition > :last-child, html .md-typeset details > :last-child {\n  margin-bottom: 0.6rem;\n}\n.md-typeset .admonition-title, .md-typeset summary {\n  position: relative;\n  margin: 0 -0.6rem 0 -0.8rem;\n  padding: 0.4rem 0.6rem 0.4rem 2rem;\n  font-weight: 700;\n  background-color: rgba(68, 138, 255, 0.1);\n  border-left: 0.2rem solid #448aff;\n}\n[dir=rtl] .md-typeset .admonition-title, [dir=rtl] .md-typeset summary {\n  margin: 0 -0.8rem 0 -0.6rem;\n  padding: 0.4rem 2rem 0.4rem 0.6rem;\n  border-right: 0.2rem solid #448aff;\n  border-left: none;\n}\nhtml .md-typeset .admonition-title:last-child, html .md-typeset summary:last-child {\n  margin-bottom: 0;\n}\n.md-typeset .admonition-title::before, .md-typeset summary::before {\n  position: absolute;\n  left: 0.6rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .admonition-title::before, [dir=rtl] .md-typeset summary::before {\n  right: 0.6rem;\n  left: initial;\n}\n.md-typeset .admonition-title code, .md-typeset summary code {\n  margin: initial;\n  padding: initial;\n  color: currentColor;\n  background-color: transparent;\n  border-radius: initial;\n  box-shadow: none;\n}\n.md-typeset .admonition-title + .tabbed-set:last-child, .md-typeset summary + .tabbed-set:last-child {\n  margin-top: 0;\n}\n\n.md-typeset .admonition.note, .md-typeset details.note {\n  border-color: #448aff;\n}\n\n.md-typeset .note > .admonition-title, .md-typeset .note > summary {\n  background-color: rgba(68, 138, 255, 0.1);\n  border-color: #448aff;\n}\n.md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before {\n  background-color: #448aff;\n  mask-image: var(--md-admonition-icon--note);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.abstract, .md-typeset details.abstract, .md-typeset .admonition.tldr, .md-typeset details.tldr, .md-typeset .admonition.summary, .md-typeset details.summary {\n  border-color: #00b0ff;\n}\n\n.md-typeset .abstract > .admonition-title, .md-typeset .abstract > summary, .md-typeset .tldr > .admonition-title, .md-typeset .tldr > summary, .md-typeset .summary > .admonition-title, .md-typeset .summary > summary {\n  background-color: rgba(0, 176, 255, 0.1);\n  border-color: #00b0ff;\n}\n.md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before, .md-typeset .tldr > .admonition-title::before, .md-typeset .tldr > summary::before, .md-typeset .summary > .admonition-title::before, .md-typeset .summary > summary::before {\n  background-color: #00b0ff;\n  mask-image: var(--md-admonition-icon--abstract);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.info, .md-typeset details.info, .md-typeset .admonition.todo, .md-typeset details.todo {\n  border-color: #00b8d4;\n}\n\n.md-typeset .info > .admonition-title, .md-typeset .info > summary, .md-typeset .todo > .admonition-title, .md-typeset .todo > summary {\n  background-color: rgba(0, 184, 212, 0.1);\n  border-color: #00b8d4;\n}\n.md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before, .md-typeset .todo > .admonition-title::before, .md-typeset .todo > summary::before {\n  background-color: #00b8d4;\n  mask-image: var(--md-admonition-icon--info);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.tip, .md-typeset details.tip, .md-typeset .admonition.important, .md-typeset details.important, .md-typeset .admonition.hint, .md-typeset details.hint {\n  border-color: #00bfa5;\n}\n\n.md-typeset .tip > .admonition-title, .md-typeset .tip > summary, .md-typeset .important > .admonition-title, .md-typeset .important > summary, .md-typeset .hint > .admonition-title, .md-typeset .hint > summary {\n  background-color: rgba(0, 191, 165, 0.1);\n  border-color: #00bfa5;\n}\n.md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before, .md-typeset .important > .admonition-title::before, .md-typeset .important > summary::before, .md-typeset .hint > .admonition-title::before, .md-typeset .hint > summary::before {\n  background-color: #00bfa5;\n  mask-image: var(--md-admonition-icon--tip);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.success, .md-typeset details.success, .md-typeset .admonition.done, .md-typeset details.done, .md-typeset .admonition.check, .md-typeset details.check {\n  border-color: #00c853;\n}\n\n.md-typeset .success > .admonition-title, .md-typeset .success > summary, .md-typeset .done > .admonition-title, .md-typeset .done > summary, .md-typeset .check > .admonition-title, .md-typeset .check > summary {\n  background-color: rgba(0, 200, 83, 0.1);\n  border-color: #00c853;\n}\n.md-typeset .success > .admonition-title::before, .md-typeset .success > summary::before, .md-typeset .done > .admonition-title::before, .md-typeset .done > summary::before, .md-typeset .check > .admonition-title::before, .md-typeset .check > summary::before {\n  background-color: #00c853;\n  mask-image: var(--md-admonition-icon--success);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.question, .md-typeset details.question, .md-typeset .admonition.faq, .md-typeset details.faq, .md-typeset .admonition.help, .md-typeset details.help {\n  border-color: #64dd17;\n}\n\n.md-typeset .question > .admonition-title, .md-typeset .question > summary, .md-typeset .faq > .admonition-title, .md-typeset .faq > summary, .md-typeset .help > .admonition-title, .md-typeset .help > summary {\n  background-color: rgba(100, 221, 23, 0.1);\n  border-color: #64dd17;\n}\n.md-typeset .question > .admonition-title::before, .md-typeset .question > summary::before, .md-typeset .faq > .admonition-title::before, .md-typeset .faq > summary::before, .md-typeset .help > .admonition-title::before, .md-typeset .help > summary::before {\n  background-color: #64dd17;\n  mask-image: var(--md-admonition-icon--question);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.warning, .md-typeset details.warning, .md-typeset .admonition.attention, .md-typeset details.attention, .md-typeset .admonition.caution, .md-typeset details.caution {\n  border-color: #ff9100;\n}\n\n.md-typeset .warning > .admonition-title, .md-typeset .warning > summary, .md-typeset .attention > .admonition-title, .md-typeset .attention > summary, .md-typeset .caution > .admonition-title, .md-typeset .caution > summary {\n  background-color: rgba(255, 145, 0, 0.1);\n  border-color: #ff9100;\n}\n.md-typeset .warning > .admonition-title::before, .md-typeset .warning > summary::before, .md-typeset .attention > .admonition-title::before, .md-typeset .attention > summary::before, .md-typeset .caution > .admonition-title::before, .md-typeset .caution > summary::before {\n  background-color: #ff9100;\n  mask-image: var(--md-admonition-icon--warning);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.failure, .md-typeset details.failure, .md-typeset .admonition.missing, .md-typeset details.missing, .md-typeset .admonition.fail, .md-typeset details.fail {\n  border-color: #ff5252;\n}\n\n.md-typeset .failure > .admonition-title, .md-typeset .failure > summary, .md-typeset .missing > .admonition-title, .md-typeset .missing > summary, .md-typeset .fail > .admonition-title, .md-typeset .fail > summary {\n  background-color: rgba(255, 82, 82, 0.1);\n  border-color: #ff5252;\n}\n.md-typeset .failure > .admonition-title::before, .md-typeset .failure > summary::before, .md-typeset .missing > .admonition-title::before, .md-typeset .missing > summary::before, .md-typeset .fail > .admonition-title::before, .md-typeset .fail > summary::before {\n  background-color: #ff5252;\n  mask-image: var(--md-admonition-icon--failure);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.danger, .md-typeset details.danger, .md-typeset .admonition.error, .md-typeset details.error {\n  border-color: #ff1744;\n}\n\n.md-typeset .danger > .admonition-title, .md-typeset .danger > summary, .md-typeset .error > .admonition-title, .md-typeset .error > summary {\n  background-color: rgba(255, 23, 68, 0.1);\n  border-color: #ff1744;\n}\n.md-typeset .danger > .admonition-title::before, .md-typeset .danger > summary::before, .md-typeset .error > .admonition-title::before, .md-typeset .error > summary::before {\n  background-color: #ff1744;\n  mask-image: var(--md-admonition-icon--danger);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.bug, .md-typeset details.bug {\n  border-color: #f50057;\n}\n\n.md-typeset .bug > .admonition-title, .md-typeset .bug > summary {\n  background-color: rgba(245, 0, 87, 0.1);\n  border-color: #f50057;\n}\n.md-typeset .bug > .admonition-title::before, .md-typeset .bug > summary::before {\n  background-color: #f50057;\n  mask-image: var(--md-admonition-icon--bug);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.example, .md-typeset details.example {\n  border-color: #7c4dff;\n}\n\n.md-typeset .example > .admonition-title, .md-typeset .example > summary {\n  background-color: rgba(124, 77, 255, 0.1);\n  border-color: #7c4dff;\n}\n.md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before {\n  background-color: #7c4dff;\n  mask-image: var(--md-admonition-icon--example);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n.md-typeset .admonition.quote, .md-typeset details.quote, .md-typeset .admonition.cite, .md-typeset details.cite {\n  border-color: #9e9e9e;\n}\n\n.md-typeset .quote > .admonition-title, .md-typeset .quote > summary, .md-typeset .cite > .admonition-title, .md-typeset .cite > summary {\n  background-color: rgba(158, 158, 158, 0.1);\n  border-color: #9e9e9e;\n}\n.md-typeset .quote > .admonition-title::before, .md-typeset .quote > summary::before, .md-typeset .cite > .admonition-title::before, .md-typeset .cite > summary::before {\n  background-color: #9e9e9e;\n  mask-image: var(--md-admonition-icon--quote);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n}\n\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n.md-typeset [id^=\"fnref:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n}\n.md-typeset [id^=\"fn:\"]:target {\n  scroll-margin-top: initial;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n}\n.md-typeset .footnote {\n  color: var(--md-default-fg-color--light);\n  font-size: 0.64rem;\n}\n.md-typeset .footnote ol {\n  margin-left: 0;\n}\n.md-typeset .footnote li {\n  transition: color 125ms;\n}\n.md-typeset .footnote li:target {\n  color: var(--md-default-fg-color);\n}\n.md-typeset .footnote li:hover .footnote-backref, .md-typeset .footnote li:target .footnote-backref {\n  transform: translateX(0);\n  opacity: 1;\n}\n.md-typeset .footnote li > :first-child {\n  margin-top: 0;\n}\n.md-typeset .footnote-backref {\n  display: inline-block;\n  color: var(--md-typeset-a-color);\n  font-size: 0;\n  vertical-align: text-bottom;\n  transform: translateX(0.25rem);\n  opacity: 0;\n  transition: color 250ms, transform 250ms 250ms, opacity 125ms 250ms;\n}\n@media print {\n  .md-typeset .footnote-backref {\n    color: var(--md-typeset-a-color);\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n[dir=rtl] .md-typeset .footnote-backref {\n  transform: translateX(-0.25rem);\n}\n.md-typeset .footnote-backref:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset .footnote-backref::before {\n  display: inline-block;\n  width: 0.8rem;\n  height: 0.8rem;\n  background-color: currentColor;\n  mask-image: var(--md-footnotes-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .footnote-backref::before svg {\n  transform: scaleX(-1);\n}\n\n.md-typeset .headerlink {\n  display: inline-block;\n  margin-left: 0.5rem;\n  color: var(--md-default-fg-color--lighter);\n  opacity: 0;\n  transition: color 250ms, opacity 125ms;\n}\n@media print {\n  .md-typeset .headerlink {\n    display: none;\n  }\n}\n[dir=rtl] .md-typeset .headerlink {\n  margin-right: 0.5rem;\n  margin-left: initial;\n}\n.md-typeset :hover > .headerlink,\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus {\n  opacity: 1;\n  transition: color 250ms, opacity 125ms;\n}\n.md-typeset :target > .headerlink,\n.md-typeset .headerlink:focus,\n.md-typeset .headerlink:hover {\n  color: var(--md-accent-fg-color);\n}\n.md-typeset :target {\n  scroll-margin-top: 3.6rem;\n}\n.md-typeset h1:target,\n.md-typeset h2:target,\n.md-typeset h3:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h1:target::before,\n.md-typeset h2:target::before,\n.md-typeset h3:target::before {\n  display: block;\n  margin-top: -3.4rem;\n  padding-top: 3.4rem;\n  content: \"\";\n}\n.md-typeset h4:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h4:target::before {\n  display: block;\n  margin-top: -3.45rem;\n  padding-top: 3.45rem;\n  content: \"\";\n}\n.md-typeset h5:target,\n.md-typeset h6:target {\n  scroll-margin-top: initial;\n}\n.md-typeset h5:target::before,\n.md-typeset h6:target::before {\n  display: block;\n  margin-top: -3.6rem;\n  padding-top: 3.6rem;\n  content: \"\";\n}\n\n.md-typeset div.arithmatex {\n  overflow: auto;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset div.arithmatex {\n    margin: 0 -0.8rem;\n  }\n}\n.md-typeset div.arithmatex > * {\n  width: min-content;\n  margin: 1em auto !important;\n  padding: 0 0.8rem;\n  touch-action: auto;\n}\n\n.md-typeset del.critic,\n.md-typeset ins.critic,\n.md-typeset .critic.comment {\n  box-decoration-break: clone;\n}\n.md-typeset del.critic {\n  background-color: var(--md-typeset-del-color);\n}\n.md-typeset ins.critic {\n  background-color: var(--md-typeset-ins-color);\n}\n.md-typeset .critic.comment {\n  color: var(--md-code-hl-comment-color);\n}\n.md-typeset .critic.comment::before {\n  content: \"/* \";\n}\n.md-typeset .critic.comment::after {\n  content: \" */\";\n}\n.md-typeset .critic.block {\n  display: block;\n  margin: 1em 0;\n  padding-right: 0.8rem;\n  padding-left: 0.8rem;\n  overflow: auto;\n  box-shadow: none;\n}\n.md-typeset .critic.block > :first-child {\n  margin-top: 0.5em;\n}\n.md-typeset .critic.block > :last-child {\n  margin-bottom: 0.5em;\n}\n\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n.md-typeset details {\n  display: flow-root;\n  padding-top: 0;\n  overflow: visible;\n}\n.md-typeset details[open] > summary::after {\n  transform: rotate(90deg);\n}\n.md-typeset details:not([open]) {\n  padding-bottom: 0;\n  box-shadow: none;\n}\n.md-typeset details:not([open]) > summary {\n  border-radius: 0.1rem;\n}\n.md-typeset details::after {\n  display: table;\n  content: \"\";\n}\n.md-typeset summary {\n  display: block;\n  min-height: 1rem;\n  padding: 0.4rem 1.8rem 0.4rem 2rem;\n  border-top-left-radius: 0.1rem;\n  border-top-right-radius: 0.1rem;\n  cursor: pointer;\n}\n[dir=rtl] .md-typeset summary {\n  padding: 0.4rem 2.2rem 0.4rem 1.8rem;\n}\n.md-typeset summary:not(.focus-visible) {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset summary::after {\n  position: absolute;\n  top: 0.4rem;\n  right: 0.4rem;\n  width: 1rem;\n  height: 1rem;\n  background-color: currentColor;\n  mask-image: var(--md-details-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  transform: rotate(0deg);\n  transition: transform 250ms;\n  content: \"\";\n}\n[dir=rtl] .md-typeset summary::after {\n  right: initial;\n  left: 0.4rem;\n  transform: rotate(180deg);\n}\n.md-typeset summary::marker, .md-typeset summary::-webkit-details-marker {\n  display: none;\n}\n\n.md-typeset .emojione,\n.md-typeset .twemoji,\n.md-typeset .gemoji {\n  display: inline-flex;\n  height: 1.125em;\n  vertical-align: text-top;\n}\n.md-typeset .emojione svg,\n.md-typeset .twemoji svg,\n.md-typeset .gemoji svg {\n  width: 1.125em;\n  max-height: 100%;\n  fill: currentColor;\n}\n\n.highlight .o,\n.highlight .ow {\n  color: var(--md-code-hl-operator-color);\n}\n.highlight .p {\n  color: var(--md-code-hl-punctuation-color);\n}\n.highlight .cpf,\n.highlight .l,\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .s2,\n.highlight .si,\n.highlight .s1,\n.highlight .ss {\n  color: var(--md-code-hl-string-color);\n}\n.highlight .cp,\n.highlight .se,\n.highlight .sh,\n.highlight .sr,\n.highlight .sx {\n  color: var(--md-code-hl-special-color);\n}\n.highlight .m,\n.highlight .mb,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo {\n  color: var(--md-code-hl-number-color);\n}\n.highlight .k,\n.highlight .kd,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .kc,\n.highlight .n {\n  color: var(--md-code-hl-name-color);\n}\n.highlight .no,\n.highlight .nb,\n.highlight .bp {\n  color: var(--md-code-hl-constant-color);\n}\n.highlight .nc,\n.highlight .ne,\n.highlight .nf,\n.highlight .nn {\n  color: var(--md-code-hl-function-color);\n}\n.highlight .nd,\n.highlight .ni,\n.highlight .nl,\n.highlight .nt {\n  color: var(--md-code-hl-keyword-color);\n}\n.highlight .c,\n.highlight .cm,\n.highlight .c1,\n.highlight .ch,\n.highlight .cs,\n.highlight .sd {\n  color: var(--md-code-hl-comment-color);\n}\n.highlight .na,\n.highlight .nv,\n.highlight .vc,\n.highlight .vg,\n.highlight .vi {\n  color: var(--md-code-hl-variable-color);\n}\n.highlight .ge,\n.highlight .gr,\n.highlight .gh,\n.highlight .go,\n.highlight .gp,\n.highlight .gs,\n.highlight .gu,\n.highlight .gt {\n  color: var(--md-code-hl-generic-color);\n}\n.highlight .gd,\n.highlight .gi {\n  margin: 0 -0.125em;\n  padding: 0 0.125em;\n  border-radius: 0.1rem;\n}\n.highlight .gd {\n  background-color: var(--md-typeset-del-color);\n}\n.highlight .gi {\n  background-color: var(--md-typeset-ins-color);\n}\n.highlight .hll {\n  display: block;\n  margin: 0 -1.1764705882em;\n  padding: 0 1.1764705882em;\n  background-color: var(--md-code-hl-color);\n}\n.highlight [data-linenos]::before {\n  position: sticky;\n  left: -1.1764705882em;\n  float: left;\n  margin-right: 1.1764705882em;\n  margin-left: -1.1764705882em;\n  padding-left: 1.1764705882em;\n  color: var(--md-default-fg-color--light);\n  background-color: var(--md-code-bg-color);\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n  content: attr(data-linenos);\n  user-select: none;\n}\n\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n}\n.highlighttable tbody,\n.highlighttable td {\n  display: block;\n  padding: 0;\n}\n.highlighttable tr {\n  display: flex;\n}\n.highlighttable pre {\n  margin: 0;\n}\n.highlighttable .linenos {\n  padding: 0.7720588235em 1.1764705882em;\n  padding-right: 0;\n  font-size: 0.85em;\n  background-color: var(--md-code-bg-color);\n  user-select: none;\n}\n.highlighttable .linenodiv {\n  padding-right: 0.5882352941em;\n  box-shadow: -0.05rem 0 var(--md-default-fg-color--lightest) inset;\n}\n.highlighttable .linenodiv pre {\n  color: var(--md-default-fg-color--light);\n  text-align: right;\n}\n.highlighttable .code {\n  flex: 1;\n  overflow: hidden;\n}\n\n.md-typeset .highlighttable {\n  margin: 1em 0;\n  direction: ltr;\n  border-radius: 0.1rem;\n}\n.md-typeset .highlighttable code {\n  border-radius: 0;\n}\n@media screen and (max-width: 44.9375em) {\n  .md-typeset > .highlight {\n    margin: 1em -0.8rem;\n  }\n  .md-typeset > .highlight .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n  .md-typeset > .highlight code {\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable {\n    margin: 1em -0.8rem;\n    border-radius: 0;\n  }\n  .md-typeset > .highlighttable .hll {\n    margin: 0 -0.8rem;\n    padding: 0 0.8rem;\n  }\n}\n\n.md-typeset .keys kbd::before,\n.md-typeset .keys kbd::after {\n  position: relative;\n  margin: 0;\n  color: inherit;\n  -moz-osx-font-smoothing: initial;\n  -webkit-font-smoothing: initial;\n}\n.md-typeset .keys span {\n  padding: 0 0.2em;\n  color: var(--md-default-fg-color--light);\n}\n.md-typeset .keys .key-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-left-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-right-alt::before {\n  padding-right: 0.4em;\n  content: \"⎇\";\n}\n.md-typeset .keys .key-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-left-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-right-command::before {\n  padding-right: 0.4em;\n  content: \"⌘\";\n}\n.md-typeset .keys .key-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-left-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-right-control::before {\n  padding-right: 0.4em;\n  content: \"⌃\";\n}\n.md-typeset .keys .key-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-left-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-right-meta::before {\n  padding-right: 0.4em;\n  content: \"◆\";\n}\n.md-typeset .keys .key-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-left-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-right-option::before {\n  padding-right: 0.4em;\n  content: \"⌥\";\n}\n.md-typeset .keys .key-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-left-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-right-shift::before {\n  padding-right: 0.4em;\n  content: \"⇧\";\n}\n.md-typeset .keys .key-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-left-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-right-super::before {\n  padding-right: 0.4em;\n  content: \"❖\";\n}\n.md-typeset .keys .key-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-left-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-right-windows::before {\n  padding-right: 0.4em;\n  content: \"⊞\";\n}\n.md-typeset .keys .key-arrow-down::before {\n  padding-right: 0.4em;\n  content: \"↓\";\n}\n.md-typeset .keys .key-arrow-left::before {\n  padding-right: 0.4em;\n  content: \"←\";\n}\n.md-typeset .keys .key-arrow-right::before {\n  padding-right: 0.4em;\n  content: \"→\";\n}\n.md-typeset .keys .key-arrow-up::before {\n  padding-right: 0.4em;\n  content: \"↑\";\n}\n.md-typeset .keys .key-backspace::before {\n  padding-right: 0.4em;\n  content: \"⌫\";\n}\n.md-typeset .keys .key-backtab::before {\n  padding-right: 0.4em;\n  content: \"⇤\";\n}\n.md-typeset .keys .key-caps-lock::before {\n  padding-right: 0.4em;\n  content: \"⇪\";\n}\n.md-typeset .keys .key-clear::before {\n  padding-right: 0.4em;\n  content: \"⌧\";\n}\n.md-typeset .keys .key-context-menu::before {\n  padding-right: 0.4em;\n  content: \"☰\";\n}\n.md-typeset .keys .key-delete::before {\n  padding-right: 0.4em;\n  content: \"⌦\";\n}\n.md-typeset .keys .key-eject::before {\n  padding-right: 0.4em;\n  content: \"⏏\";\n}\n.md-typeset .keys .key-end::before {\n  padding-right: 0.4em;\n  content: \"⤓\";\n}\n.md-typeset .keys .key-escape::before {\n  padding-right: 0.4em;\n  content: \"⎋\";\n}\n.md-typeset .keys .key-home::before {\n  padding-right: 0.4em;\n  content: \"⤒\";\n}\n.md-typeset .keys .key-insert::before {\n  padding-right: 0.4em;\n  content: \"⎀\";\n}\n.md-typeset .keys .key-page-down::before {\n  padding-right: 0.4em;\n  content: \"⇟\";\n}\n.md-typeset .keys .key-page-up::before {\n  padding-right: 0.4em;\n  content: \"⇞\";\n}\n.md-typeset .keys .key-print-screen::before {\n  padding-right: 0.4em;\n  content: \"⎙\";\n}\n.md-typeset .keys .key-tab::after {\n  padding-left: 0.4em;\n  content: \"⇥\";\n}\n.md-typeset .keys .key-num-enter::after {\n  padding-left: 0.4em;\n  content: \"⌤\";\n}\n.md-typeset .keys .key-enter::after {\n  padding-left: 0.4em;\n  content: \"⏎\";\n}\n\n.md-typeset .tabbed-content {\n  display: none;\n  order: 99;\n  width: 100%;\n  box-shadow: 0 -0.05rem var(--md-default-fg-color--lightest);\n}\n@media print {\n  .md-typeset .tabbed-content {\n    display: block;\n    order: initial;\n  }\n}\n.md-typeset .tabbed-content > pre:only-child,\n.md-typeset .tabbed-content > .highlight:only-child pre,\n.md-typeset .tabbed-content > .highlighttable:only-child {\n  margin: 0;\n}\n.md-typeset .tabbed-content > pre:only-child > code,\n.md-typeset .tabbed-content > .highlight:only-child pre > code,\n.md-typeset .tabbed-content > .highlighttable:only-child > code {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.md-typeset .tabbed-content > .tabbed-set {\n  margin: 0;\n}\n.md-typeset .tabbed-set {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 1em 0;\n  border-radius: 0.1rem;\n}\n.md-typeset .tabbed-set > input {\n  position: absolute;\n  width: 0;\n  height: 0;\n  opacity: 0;\n}\n.md-typeset .tabbed-set > input:checked + label {\n  color: var(--md-accent-fg-color);\n  border-color: var(--md-accent-fg-color);\n}\n.md-typeset .tabbed-set > input:checked + label + .tabbed-content {\n  display: block;\n}\n.md-typeset .tabbed-set > input:focus + label {\n  outline-style: auto;\n}\n.md-typeset .tabbed-set > input:not(.focus-visible) + label {\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.md-typeset .tabbed-set > label {\n  z-index: 1;\n  width: auto;\n  padding: 0.9375em 1.25em 0.78125em;\n  color: var(--md-default-fg-color--light);\n  font-weight: 700;\n  font-size: 0.64rem;\n  border-bottom: 0.1rem solid transparent;\n  cursor: pointer;\n  transition: color 250ms;\n}\n.md-typeset .tabbed-set > label:hover {\n  color: var(--md-accent-fg-color);\n}\n\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n.md-typeset .task-list-item {\n  position: relative;\n  list-style-type: none;\n}\n.md-typeset .task-list-item [type=checkbox] {\n  position: absolute;\n  top: 0.45em;\n  left: -2em;\n}\n[dir=rtl] .md-typeset .task-list-item [type=checkbox] {\n  right: -2em;\n  left: initial;\n}\n.md-typeset .task-list-control [type=checkbox] {\n  z-index: -1;\n  opacity: 0;\n}\n.md-typeset .task-list-indicator::before {\n  position: absolute;\n  top: 0.15em;\n  left: -1.5em;\n  width: 1.25em;\n  height: 1.25em;\n  background-color: var(--md-default-fg-color--lightest);\n  mask-image: var(--md-tasklist-icon);\n  mask-repeat: no-repeat;\n  mask-size: contain;\n  content: \"\";\n}\n[dir=rtl] .md-typeset .task-list-indicator::before {\n  right: -1.5em;\n  left: initial;\n}\n.md-typeset [type=checkbox]:checked + .task-list-indicator::before {\n  background-color: #00e676;\n  mask-image: var(--md-tasklist-icon--checked);\n}\n\n@media screen and (min-width: 45em) {\n  .md-typeset .inline {\n    float: left;\n    width: 11.7rem;\n    margin-top: 0;\n    margin-right: 0.8rem;\n    margin-bottom: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  .md-typeset .inline.end {\n    float: right;\n    margin-right: 0;\n    margin-left: 0.8rem;\n  }\n  [dir=rtl] .md-typeset .inline.end {\n    float: left;\n    margin-right: 0.8rem;\n    margin-left: 0;\n  }\n}\n\n/*# sourceMappingURL=main.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Enforce correct box model and prevent adjustments of font size after\n// orientation changes in IE and iOS\nhtml {\n  box-sizing: border-box;\n  text-size-adjust: none;\n}\n\n// All elements shall inherit the document default\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\n// Remove margin in all browsers\nbody {\n  margin: 0;\n}\n\n// Reset tap outlines on iOS and Android\na,\nbutton,\nlabel,\ninput {\n  -webkit-tap-highlight-color: transparent;\n}\n\n// Reset link styles\na {\n  color: inherit;\n  text-decoration: none;\n}\n\n// Normalize horizontal separator styles\nhr {\n  display: block;\n  box-sizing: content-box;\n  height: px2rem(1px);\n  padding: 0;\n  overflow: visible;\n  border: 0;\n}\n\n// Normalize font-size in all browsers\nsmall {\n  font-size: 80%;\n}\n\n// Prevent subscript and superscript from affecting line-height\nsub,\nsup {\n  line-height: 1em;\n}\n\n// Remove border on image\nimg {\n  border-style: none;\n}\n\n// Reset table styles\ntable {\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\n// Reset table cell styles\ntd,\nth {\n  font-weight: 400;\n  vertical-align: top;\n}\n\n// Reset button styles\nbutton {\n  margin: 0;\n  padding: 0;\n  font-size: inherit;\n  background: transparent;\n  border: 0;\n}\n\n// Reset input styles\ninput {\n  border: 0;\n  outline: none;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Color definitions\n:root {\n\n  // Default color shades\n  --md-default-fg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-default-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-default-fg-color--lighter:      hsla(0, 0%, 0%, 0.32);\n  --md-default-fg-color--lightest:     hsla(0, 0%, 0%, 0.07);\n  --md-default-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-default-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-default-bg-color--lighter:      hsla(0, 0%, 100%, 0.3);\n  --md-default-bg-color--lightest:     hsla(0, 0%, 100%, 0.12);\n\n  // Primary color shades\n  --md-primary-fg-color:               hsla(#{hex2hsl($clr-indigo-500)}, 1);\n  --md-primary-fg-color--light:        hsla(#{hex2hsl($clr-indigo-400)}, 1);\n  --md-primary-fg-color--dark:         hsla(#{hex2hsl($clr-indigo-700)}, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Accent color shades\n  --md-accent-fg-color:                hsla(#{hex2hsl($clr-indigo-a200)}, 1);\n  --md-accent-fg-color--transparent:   hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);\n  --md-accent-bg-color:                hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light:         hsla(0, 0%, 100%, 0.7);\n\n  // Light theme (default)\n  > * {\n\n    // Code color shades\n    --md-code-fg-color:                hsla(200, 18%, 26%, 1);\n    --md-code-bg-color:                hsla(0, 0%, 96%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n    --md-code-hl-number-color:         hsla(0, 67%, 50%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 47%, 1);\n    --md-code-hl-function-color:       hsla(291, 45%, 50%, 1);\n    --md-code-hl-constant-color:       hsla(250, 63%, 60%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 54%, 51%, 1);\n    --md-code-hl-string-color:         hsla(150, 63%, 30%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);\n\n    // Typeset `del` and `ins` color shades\n    --md-typeset-del-color:            hsla(6, 90%, 60%, 0.15);\n    --md-typeset-ins-color:            hsla(150, 90%, 44%, 0.15);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(0, 0%, 98%, 1);\n    --md-typeset-kbd-accent-color:     hsla(0, 100%, 100%, 1);\n    --md-typeset-kbd-border-color:     hsla(0, 0%, 72%, 1);\n\n    // Admonition color shades\n    --md-admonition-fg-color:          var(--md-default-fg-color);\n    --md-admonition-bg-color:          var(--md-default-bg-color);\n\n    // Footer color shades\n    --md-footer-fg-color:              hsla(0, 0%, 100%, 1);\n    --md-footer-fg-color--light:       hsla(0, 0%, 100%, 0.7);\n    --md-footer-fg-color--lighter:     hsla(0, 0%, 100%, 0.3);\n    --md-footer-bg-color:              hsla(0, 0%, 0%, 0.87);\n    --md-footer-bg-color--dark:        hsla(0, 0%, 0%, 0.32);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon\n.md-icon {\n\n  // SVG defaults\n  svg {\n    display: block;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    fill: currentColor;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: font definitions\n// ----------------------------------------------------------------------------\n\n// Enable font-smoothing in Webkit and FF\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// Define default fonts\nbody,\ninput {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\", \"liga\";\n  font-family:\n    var(--md-text-font-family, _),\n    -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\n}\n\n// Define monospaced fonts\ncode,\npre,\nkbd {\n  color: var(--md-typeset-color);\n  font-feature-settings: \"kern\";\n  font-family:\n    var(--md-code-font-family, _),\n    SFMono-Regular, Consolas, Menlo, monospace;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: typesetted content\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-typeset-table--ascending: svg-load(\"material/arrow-down.svg\");\n  --md-typeset-table--descending: svg-load(\"material/arrow-up.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Content that is typeset - if possible, all margins, paddings and font sizes\n// should be set in ems, so nested blocks (e.g. admonitions) render correctly.\n.md-typeset {\n  font-size: px2rem(16px);\n  line-height: 1.6;\n  color-adjust: exact;\n\n  // [print]: We'll use a smaller `font-size` for printing, so code examples\n  // don't break too early, and `16px` looks too big anyway.\n  @media print {\n    font-size: px2rem(13.6px);\n  }\n\n  // Default spacing\n  ul,\n  ol,\n  dl,\n  figure,\n  blockquote,\n  pre {\n    display: flow-root;\n    margin: 1em 0;\n  }\n\n  // Headline on level 1\n  h1 {\n    margin: 0 0 px2em(40px, 32px);\n    color: var(--md-default-fg-color--light);\n    font-weight: 300;\n    font-size: px2em(32px);\n    line-height: 1.3;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 2\n  h2 {\n    margin: px2em(40px, 25px) 0 px2em(16px, 25px);\n    font-weight: 300;\n    font-size: px2em(25px);\n    line-height: 1.4;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3\n  h3 {\n    margin: px2em(32px, 20px) 0 px2em(16px, 20px);\n    font-weight: 400;\n    font-size: px2em(20px);\n    line-height: 1.5;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 3 following level 2\n  h2 + h3 {\n    margin-top: px2em(16px, 20px);\n  }\n\n  // Headline on level 4\n  h4 {\n    margin: px2em(16px) 0;\n    font-weight: 700;\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5-6\n  h5,\n  h6 {\n    margin: px2em(16px, 12.8px) 0;\n    color: var(--md-default-fg-color--light);\n    font-weight: 700;\n    font-size: px2em(12.8px);\n    letter-spacing: -0.01em;\n  }\n\n  // Headline on level 5\n  h5 {\n    text-transform: uppercase;\n  }\n\n  // Horizontal separator\n  hr {\n    display: flow-root;\n    margin: 1.5em 0;\n    border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);\n  }\n\n  // Text link\n  a {\n    color: var(--md-typeset-a-color);\n    word-break: break-word;\n\n    // Also enable color transition on pseudo elements\n    &,\n    &::before {\n      transition: color 125ms;\n    }\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Code block\n  code,\n  pre,\n  kbd {\n    color: var(--md-code-fg-color);\n    direction: ltr;\n\n    // [print]: Wrap text and hide scollbars\n    @media print {\n      white-space: pre-wrap;\n    }\n  }\n\n  // Inline code block\n  code {\n    padding: 0 px2em(4px, 13.6px);\n    font-size: px2em(13.6px);\n    word-break: break-word;\n    background-color: var(--md-code-bg-color);\n    border-radius: px2rem(2px);\n    box-decoration-break: clone;\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n  }\n\n  // Code block in headline\n  h1 code,\n  h2 code,\n  h3 code,\n  h4 code,\n  h5 code,\n  h6 code {\n    margin: initial;\n    padding: initial;\n    background-color: transparent;\n    box-shadow: none;\n  }\n\n  // Ensure link color in code blocks\n  a > code {\n    color: currentColor;\n  }\n\n  // Unformatted content\n  pre {\n    position: relative;\n    line-height: 1.4;\n\n    // Code block\n    > code {\n      display: block;\n      margin: 0;\n      padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n      overflow: auto;\n      word-break: normal;\n      box-shadow: none;\n      box-decoration-break: slice;\n      touch-action: auto;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Code block on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Unformatted text\n    > pre {\n      margin: 1em px2rem(-16px);\n\n      // Code block\n      code {\n        border-radius: 0;\n      }\n    }\n  }\n\n  // Keyboard key\n  kbd {\n    display: inline-block;\n    padding: 0 px2em(8px, 12px);\n    color: var(--md-default-fg-color);\n    font-size: px2em(12px);\n    vertical-align: text-top;\n    word-break: break-word;\n    background-color: var(--md-typeset-kbd-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(2px)  0 px2rem(1px) var(--md-typeset-kbd-border-color),\n      0 px2rem(2px)  0             var(--md-typeset-kbd-border-color),\n      0 px2rem(-2px) px2rem(4px)   var(--md-typeset-kbd-accent-color) inset;\n  }\n\n  // Text highlighting marker\n  mark {\n    color: inherit;\n    word-break: break-word;\n    background-color: var(--md-typeset-mark-color);\n    box-decoration-break: clone;\n  }\n\n  // Abbreviation\n  abbr {\n    text-decoration: none;\n    border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);\n    cursor: help;\n\n    // Show tooltip for touch devices\n    @media (hover: none) {\n      position: relative;\n\n      // Tooltip\n      &[title]:focus::after,\n      &[title]:hover::after {\n        @include z-depth(2);\n\n        position: absolute;\n        left: 0;\n        display: inline-block;\n        width: auto;\n        min-width: max-content;\n        max-width: 80%;\n        margin-top: 2em;\n        padding: px2rem(4px) px2rem(6px);\n        color: var(--md-default-bg-color);\n        font-size: px2rem(14px);\n        background-color: var(--md-default-fg-color);\n        border-radius: px2rem(2px);\n        content: attr(title);\n      }\n    }\n  }\n\n  // Small text\n  small {\n    opacity: 0.75;\n  }\n\n  // Superscript and subscript\n  sup,\n  sub {\n    margin-left: px2em(1px, 12.8px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(1px, 12.8px);\n      margin-left: initial;\n    }\n  }\n\n  // Blockquotes, possibly nested\n  blockquote {\n    padding-left: px2rem(12px);\n    color: var(--md-default-fg-color--light);\n    border-left: px2rem(4px) solid var(--md-default-fg-color--lighter);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: initial;\n      border-right: px2rem(4px) solid var(--md-default-fg-color--lighter);\n      border-left: initial;\n    }\n  }\n\n  // Unordered list\n  ul {\n    list-style-type: disc;\n  }\n\n  // Unordered and ordered list\n  ul,\n  ol {\n    margin-left: px2em(10px);\n    padding: 0;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(10px);\n      margin-left: initial;\n    }\n\n    // Nested ordered list\n    ol {\n      list-style-type: lower-alpha;\n\n      // Triply nested ordered list\n      ol {\n        list-style-type: lower-roman;\n      }\n    }\n\n    // List element\n    li {\n      margin-bottom: 0.5em;\n      margin-left: px2em(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2em(20px);\n        margin-left: initial;\n      }\n\n      // Adjust spacing\n      p,\n      blockquote {\n        margin: 0.5em 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Nested list\n      ul,\n      ol {\n        margin: 0.5em 0 0.5em px2em(10px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2em(10px);\n          margin-left: initial;\n        }\n      }\n    }\n  }\n\n  // Definition list\n  dd {\n    margin: 1em 0 1.5em px2em(30px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2em(30px);\n      margin-left: initial;\n    }\n  }\n\n  // Image or icon\n  img,\n  svg {\n    max-width: 100%;\n    height: auto;\n\n    // Adjust spacing when left-aligned\n    &[align=\"left\"] {\n      margin: 1em;\n      margin-left: 0;\n    }\n\n    // Adjust spacing when right-aligned\n    &[align=\"right\"] {\n      margin: 1em;\n      margin-right: 0;\n    }\n\n    // Adjust spacing when sole children\n    &[align]:only-child {\n      margin-top: 0;\n    }\n  }\n\n  // Figure\n  figure {\n    width: fit-content;\n    max-width: 100%;\n    margin: 0 auto;\n    text-align: center;\n\n    // Figure images\n    img {\n      display: block;\n    }\n  }\n\n  // Figure caption\n  figcaption {\n    max-width: px2rem(480px);\n    margin: 1em auto 2em;\n    font-style: italic;\n  }\n\n  // Limit width to container\n  iframe {\n    max-width: 100%;\n  }\n\n  // Data table\n  table:not([class]) {\n    display: inline-block;\n    max-width: 100%;\n    overflow: auto;\n    font-size: px2rem(12.8px);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    touch-action: auto;\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: table;\n    }\n\n    // Due to margin collapse because of the necessary inline-block hack, we\n    // cannot increase the bottom margin on the table, so we just increase the\n    // top margin on the following element\n    + * {\n      margin-top: 1.5em;\n    }\n\n    // Elements in table heading and cell\n    th > *,\n    td > * {\n\n      // Adjust spacing on first child\n      &:first-child {\n        margin-top: 0;\n      }\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    // Table heading and cell\n    th:not([align]),\n    td:not([align]) {\n      text-align: left;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        text-align: right;\n      }\n    }\n\n    // Table heading\n    th {\n      min-width: px2rem(100px);\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      color: var(--md-default-bg-color);\n      vertical-align: top;\n      background-color: var(--md-default-fg-color--light);\n\n      // Links in table headings\n      a {\n        color: inherit;\n      }\n    }\n\n    // Table cell\n    td {\n      padding: px2em(12px, 12.8px) px2em(16px, 12.8px);\n      vertical-align: top;\n      border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n    }\n\n    // Table row\n    tr {\n      transition: background-color 125ms;\n\n      // Table row on hover\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.035);\n        box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;\n      }\n\n      // Hide border on first table row\n      &:first-child td {\n        border-top: 0;\n      }\n    }\n\n    // Text link in table\n    a {\n      word-break: normal;\n    }\n  }\n\n  // Sortable table\n  table th[role=\"columnheader\"] {\n    cursor: pointer;\n\n    // Sort icon\n    &::after {\n      display: inline-block;\n      width: 1.2em;\n      height: 1.2em;\n      margin-left: 0.5em;\n      vertical-align: sub;\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Sort ascending\n    &[aria-sort=\"ascending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--ascending);\n    }\n\n    // Sort descending\n    &[aria-sort=\"descending\"]::after {\n      background-color: currentColor;\n      mask-image: var(--md-typeset-table--descending);\n    }\n  }\n\n  // Data table scroll wrapper\n  &__scrollwrap {\n    margin: 1em px2rem(-16px);\n    overflow-x: auto;\n    touch-action: auto;\n  }\n\n  // Data table wrapper\n  &__table {\n    display: inline-block;\n    margin-bottom: 0.5em;\n    padding: 0 px2rem(16px);\n\n    // [print]: Reset display mode so table header wraps when printing\n    @media print {\n      display: block;\n    }\n\n    // Data table\n    html & table {\n      display: table;\n      width: 100%;\n      margin: 0;\n      overflow: hidden;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","//\n// Name:           Material Shadows\n// Description:    Mixins for Material Design Shadows.\n// Version:        3.0.1\n//\n// Author:         Denis Malinochkin\n// Git:            https://github.com/mrmlnc/material-shadows\n//\n// twitter:        @mrmlnc\n//\n// ------------------------------------\n\n\n// Mixins\n// ------------------------------------\n\n@mixin z-depth-transition() {\n  transition: box-shadow .28s cubic-bezier(.4, 0, .2, 1);\n}\n\n@mixin z-depth-focus() {\n  box-shadow: 0 0 8px rgba(0, 0, 0, .18), 0 8px 16px rgba(0, 0, 0, .36);\n}\n\n@mixin z-depth-2dp() {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14),\n              0 1px 5px 0 rgba(0, 0, 0, .12),\n              0 3px 1px -2px rgba(0, 0, 0, .2);\n}\n\n@mixin z-depth-3dp() {\n  box-shadow: 0 3px 4px 0 rgba(0, 0, 0, .14),\n              0 1px 8px 0 rgba(0, 0, 0, .12),\n              0 3px 3px -2px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-4dp() {\n  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14),\n              0 1px 10px 0 rgba(0, 0, 0, .12),\n              0 2px 4px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-6dp() {\n  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14),\n              0 1px 18px 0 rgba(0, 0, 0, .12),\n              0 3px 5px -1px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-8dp() {\n  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14),\n              0 3px 14px 2px rgba(0, 0, 0, .12),\n              0 5px 5px -3px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-16dp() {\n  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14),\n              0  6px 30px 5px rgba(0, 0, 0, .12),\n              0  8px 10px -5px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth-24dp() {\n  box-shadow: 0  9px 46px  8px rgba(0, 0, 0, .14),\n              0 24px 38px  3px rgba(0, 0, 0, .12),\n              0 11px 15px -7px rgba(0, 0, 0, .4);\n}\n\n@mixin z-depth($dp: 2) {\n  @if $dp == 2 {\n    @include z-depth-2dp();\n  } @else if $dp == 3 {\n    @include z-depth-3dp();\n  } @else if $dp == 4 {\n    @include z-depth-4dp();\n  } @else if $dp == 6 {\n    @include z-depth-6dp();\n  } @else if $dp == 8 {\n    @include z-depth-8dp();\n  } @else if $dp == 16 {\n    @include z-depth-16dp();\n  } @else if $dp == 24 {\n    @include z-depth-24dp();\n  }\n}\n\n\n// Class generator\n// ------------------------------------\n\n@mixin z-depth-classes($transition: false, $focus: false) {\n  @if $transition == true {\n    &-transition {\n      @include z-depth-transition();\n    }\n  }\n\n  @if $focus == true {\n    &-focus {\n      @include z-depth-focus();\n    }\n  }\n\n  // The available values for the shadow depth\n  @each $depth in 2, 3, 4, 6, 8, 16, 24 {\n    &-#{$depth}dp {\n      @include z-depth($depth);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: base grid and containers\n// ----------------------------------------------------------------------------\n\n// Stretch container to viewport and set base `font-size`\nhtml {\n  height: 100%;\n  overflow-x: hidden;\n  // Hack: normally, we would set the base `font-size` to `62.5%`, so we can\n  // base all calculations on `10px`, but Chromium and Chrome define a minimal\n  // `font-size` of `12px` if the system language is set to Chinese. For this\n  // reason we just double the `font-size` and set it to `20px`.\n  //\n  // See https://github.com/squidfunk/mkdocs-material/issues/911\n  font-size: 125%;\n\n  // [screen medium +]: Set base `font-size` to `11px`\n  @include break-from-device(screen medium) {\n    font-size: 137.5%;\n  }\n\n  // [screen large +]: Set base `font-size` to `12px`\n  @include break-from-device(screen large) {\n    font-size: 150%;\n  }\n}\n\n// Stretch body to container - flexbox is used, so the footer will always be\n// aligned to the bottom of the viewport\nbody {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  min-height: 100%;\n  // Hack: reset `font-size` to `10px`, so the spacing for all inline elements\n  // is correct again. Otherwise the spacing would be based on `20px`.\n  font-size: px2rem(10px);\n  background-color: var(--md-default-bg-color);\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n\n  // Body in locked state\n  &[data-md-state=\"lock\"] {\n\n    // [tablet portrait -]: Omit scroll bubbling\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Grid container - this class is applied to wrapper elements within the\n// header, content area and footer, and makes sure that their width is limited\n// to `1220px`, and they are rendered centered if the screen is larger.\n.md-grid {\n  max-width: px2rem(1220px);\n  margin-right: auto;\n  margin-left: auto;\n}\n\n// Main container\n.md-container {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n\n  // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)\n  @media print {\n    display: block;\n  }\n}\n\n// Main area - stretch to remaining space of container\n.md-main {\n  flex-grow: 1;\n\n  // Main area wrapper\n  &__inner {\n    display: flex;\n    height: 100%;\n    margin-top: px2rem(24px + 6px);\n  }\n}\n\n// Add ellipsis in case of overflowing text\n.md-ellipsis {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n// ----------------------------------------------------------------------------\n// Rules: navigational elements\n// ----------------------------------------------------------------------------\n\n// Toggle - this class is applied to the checkbox elements, which are used to\n// implement the CSS-only drawer and navigation, as well as the search\n.md-toggle {\n  display: none;\n}\n\n// Skip link\n.md-skip {\n  position: fixed;\n  // Hack: if we don't set the negative `z-index`, the skip link will force the\n  // creation of new layers when code blocks are near the header on scrolling\n  z-index: -1;\n  margin: px2rem(10px);\n  padding: px2rem(6px) px2rem(10px);\n  color: var(--md-default-bg-color);\n  font-size: px2rem(12.8px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(px2rem(8px));\n  opacity: 0;\n\n  // Show skip link on focus\n  &:focus {\n    z-index: 10;\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n      opacity   175ms 75ms;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: print styles\n// ----------------------------------------------------------------------------\n\n// Add margins to page\n@page {\n  margin: 25mm;\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Announcement bar\n.md-announce {\n  overflow: auto;\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide announcement bar\n  @media print {\n    display: none;\n  }\n\n  // Announcement wrapper\n  &__inner {\n    margin: px2rem(12px) auto;\n    padding: 0 px2rem(16px);\n    color: var(--md-footer-fg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-clipboard-icon: svg-load(\"material/content-copy.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Button to copy to clipboard\n.md-clipboard {\n  position: absolute;\n  top: px2em(8px);\n  right: px2em(8px);\n  z-index: 1;\n  width: px2em(24px);\n  height: px2em(24px);\n  color: var(--md-default-fg-color--lightest);\n  border-radius: px2rem(2px);\n  cursor: pointer;\n  transition: color 250ms;\n\n  // [print]: Hide button\n  @media print {\n    display: none;\n  }\n\n  // Hide outline for pointer devices\n  &:not(.focus-visible) {\n    outline: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  // Darken color on code block hover\n  :hover > & {\n    color: var(--md-default-fg-color--light);\n  }\n\n  // Button on focus/hover\n  &:focus,\n  &:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Button icon - the width and height are defined in `em`, so the size is\n  // automatically adjusted for nested code blocks (e.g. in admonitions)\n  &::after {\n    display: block;\n    width: px2em(18px);\n    height: px2em(18px);\n    margin: 0 auto;\n    background-color: currentColor;\n    mask-image: var(--md-clipboard-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n  }\n\n  // Inline button\n  &--inline {\n    cursor: pointer;\n\n    // Code block\n    code {\n      transition:\n        color            250ms,\n        background-color 250ms;\n    }\n\n    // Code block on focus/hover\n    &:focus code,\n    &:hover code {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Content area\n.md-content {\n  flex-grow: 1;\n  // Hack: we must use `overflow: hidden`, so the content area is capped by\n  // the dimensions of its parent. Otherwise, long code blocks might lead to\n  // a wider content area which will break everything. This, however, induces\n  // margin collapse, which will break scroll margins. Adding a large enough\n  // scroll padding seems to do the trick, at least in Chrome and Firefox.\n  overflow: hidden;\n  scroll-padding-top: px2rem(1024px);\n\n  // Content wrapper\n  &__inner {\n    margin: 0 px2rem(16px) px2rem(24px);\n    padding-top: px2rem(12px);\n\n    // [screen +]: Adjust spacing between content area and sidebars\n    @include break-from-device(screen) {\n\n      // Sidebar with navigation is visible\n      .md-sidebar--primary:not([hidden]) ~ .md-content > & {\n        margin-left: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(24px);\n          margin-left: px2rem(16px);\n        }\n      }\n\n      // Sidebar with table of contents is visible\n      .md-sidebar--secondary:not([hidden]) ~ .md-content > & {\n        margin-right: px2rem(24px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          margin-right: px2rem(16px);\n          margin-left: px2rem(24px);\n        }\n      }\n    }\n\n    // Hack: add pseudo element for spacing, as the overflow of the content\n    // container may not be hidden due to an imminent offset error on targets\n    &::before {\n      display: block;\n      height: px2rem(8px);\n      content: \"\";\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  // Button inside of the content area - these buttons are meant for actions on\n  // a document-level, i.e. linking to related source code files, printing etc.\n  &__button {\n    float: right;\n    margin: px2rem(8px) 0;\n    margin-left: px2rem(8px);\n    padding: 0;\n\n    // [print]: Hide buttons\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      float: left;\n      margin-right: px2rem(8px);\n      margin-left: initial;\n\n      // Flip icon vertically\n      svg {\n        transform: scaleX(-1);\n      }\n    }\n\n    // Adjust default link color for icons\n    .md-typeset & {\n      color: var(--md-default-fg-color--lighter);\n    }\n\n    // Align with body copy located next to icon\n    svg {\n      display: inline;\n      vertical-align: top;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Dialog\n.md-dialog {\n  @include z-depth(2);\n\n  position: fixed;\n  right: px2rem(16px);\n  bottom: px2rem(16px);\n  left: initial;\n  z-index: 2;\n  min-width: px2rem(222px);\n  padding: px2rem(8px) px2rem(12px);\n  background-color: var(--md-default-fg-color);\n  border-radius: px2rem(2px);\n  transform: translateY(100%);\n  opacity: 0;\n  transition:\n    transform 0ms   400ms,\n    opacity   400ms;\n  pointer-events: none;\n\n  // [print]: Hide dialog\n  @media print {\n    display: none;\n  }\n\n  // Adjust for right-to-left languages\n  [dir=\"rtl\"] & {\n    right: initial;\n    left: px2rem(16px);\n  }\n\n  // Dialog in open state\n  &[data-md-state=\"open\"] {\n    transform: translateY(0);\n    opacity: 1;\n    transition:\n      transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),\n      opacity   400ms;\n    pointer-events: initial;\n  }\n\n  // Dialog wrapper\n  &__inner {\n    color: var(--md-default-bg-color);\n    font-size: px2rem(14px);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Form button\n  .md-button {\n    display: inline-block;\n    padding: px2em(10px) px2em(32px);\n    color: var(--md-primary-fg-color);\n    font-weight: 700;\n    border: px2rem(2px) solid currentColor;\n    border-radius: px2rem(2px);\n    transition:\n      color            125ms,\n      background-color 125ms,\n      border-color     125ms;\n\n    // Primary button\n    &--primary {\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color);\n      border-color: var(--md-primary-fg-color);\n    }\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-bg-color);\n      background-color: var(--md-accent-fg-color);\n      border-color: var(--md-accent-fg-color);\n    }\n  }\n\n  // Form input\n  .md-input {\n    height: px2rem(36px);\n    padding: 0 px2rem(12px);\n    font-size: px2rem(16px);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.1);\n    transition: box-shadow 250ms;\n\n    // Input on focus/hover\n    &:focus,\n    &:hover {\n      box-shadow:\n        0 px2rem(8px)   px2rem(20px) hsla(0, 0%, 0%, 0.15),\n        0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.15);\n    }\n\n    // Stretch to full width\n    &--stretch {\n      width: 100%;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Header - by default, the header will be sticky and stay always on top of the\n// viewport. If this behavior is not desired, just set `position: static`.\n.md-header {\n  position: sticky;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 2;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  // Hack: reduce jitter by adding a transparent box shadow of the same size\n  // so the size of the layer doesn't change during animation\n  box-shadow:\n    0 0           px2rem(4px) rgba(0, 0, 0, 0),\n    0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);\n  transition:\n    color            250ms,\n    background-color 250ms;\n\n  // [print]: Hide header\n  @media print {\n    display: none;\n  }\n\n  // Header in shadow state, i.e. shadow is visible\n  &[data-md-state=\"shadow\"] {\n    box-shadow:\n      0 0           px2rem(4px) rgba(0, 0, 0, 0.1),\n      0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);\n    transition:\n      transform        250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n  }\n\n  // Header in hidden state, i.e. moved out of sight\n  &[data-md-state=\"hidden\"] {\n    transform: translateY(-100%);\n    transition:\n      transform        250ms cubic-bezier(0.8, 0, 0.6, 1),\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n  }\n\n  // Header wrapper\n  &__inner {\n    display: flex;\n    align-items: center;\n    padding: 0 px2rem(4px);\n  }\n\n  // Header button\n  &__button {\n    position: relative;\n    z-index: 1;\n    display: inline-block;\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n    color: currentColor;\n    vertical-align: middle;\n    cursor: pointer;\n    transition: opacity 250ms;\n\n    // Button on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n    }\n\n    // Button with logo, pointing to `config.site_url`\n    &.md-logo {\n      margin: px2rem(4px);\n      padding: px2rem(8px);\n\n      // [tablet -]: Hide button\n      @include break-to-device(tablet) {\n        display: none;\n      }\n\n      // Image or icon\n      img,\n      svg {\n        display: block;\n        width: px2rem(24px);\n        height: px2rem(24px);\n        fill: currentColor;\n      }\n    }\n\n    // Button for search\n    &[for=\"__search\"] {\n\n      // [tablet landscape +]: Hide button\n      @include break-from-device(tablet landscape) {\n        display: none;\n      }\n\n      // [no-js]: Hide button\n      .no-js & {\n        display: none;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Button for drawer\n    &[for=\"__drawer\"] {\n\n      // [screen +]: Hide button\n      @include break-from-device(screen) {\n        display: none;\n      }\n    }\n  }\n\n  // Header topic\n  &__topic {\n    position: absolute;\n    display: flex;\n    max-width: 100%;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   150ms;\n\n    // Second header topic - title of the current page\n    & + & {\n      z-index: -1;\n      transform: translateX(px2rem(25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(-25px));\n      }\n    }\n  }\n\n  // Header title\n  &__title {\n    flex-grow: 1;\n    height: px2rem(48px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n\n    // Header title in active state, i.e. page title is visible\n    &[data-md-state=\"active\"] .md-header__topic {\n      z-index: -1;\n      transform: translateX(px2rem(-25px));\n      opacity: 0;\n      transition:\n        transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(px2rem(25px));\n      }\n\n      // Second header topic - title of the current page\n      + .md-header__topic {\n        z-index: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms;\n        pointer-events: initial;\n      }\n    }\n\n    // Add ellipsis in case of overflowing text\n    > .md-header__ellipsis {\n      position: relative;\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  // Header options\n  &__options {\n    display: flex;\n    flex-shrink: 0;\n    max-width: 100%;\n    white-space: nowrap;\n    transition:\n      max-width  0ms 250ms,\n      opacity  250ms 250ms;\n\n    // Hide inactive buttons\n    > [data-md-state=\"hidden\"] {\n      display: none;\n    }\n\n    // Hide toggle when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      max-width: 0;\n      opacity: 0;\n      transition:\n        max-width 0ms,\n        opacity   0ms;\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n\n    // [tablet landscape +]: Show repository information\n    @include break-from-device(tablet landscape) {\n      display: block;\n      width: px2rem(234px);\n      max-width: px2rem(234px);\n      margin-left: px2rem(20px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(20px);\n        margin-left: initial;\n      }\n    }\n\n    // [screen +]: Adjust spacing of search bar\n    @include break-from-device(screen) {\n      margin-left: px2rem(28px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(28px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Footer\n.md-footer {\n  color: var(--md-footer-fg-color);\n  background-color: var(--md-footer-bg-color);\n\n  // [print]: Hide footer\n  @media print {\n    display: none;\n  }\n\n  // Footer wrapper\n  &__inner {\n    padding: px2rem(4px);\n    overflow: auto;\n  }\n\n  // Footer link to previous and next page\n  &__link {\n    display: flex;\n    padding-top: px2rem(28px);\n    padding-bottom: px2rem(8px);\n    transition: opacity 250ms;\n\n    // [tablet +]: Adjust width to 50/50\n    @include break-from-device(tablet) {\n      width: 50%;\n    }\n\n    // Footer link on focus/hover\n    &:focus,\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Footer link to previous page\n    &--prev {\n      float: left;\n\n      // [mobile -]: Adjust width to 25/75 and hide title\n      @include break-to-device(mobile) {\n        width: 25%;\n\n        // Hide footer title\n        .md-footer__title {\n          display: none;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n\n    // Footer link to next page\n    &--next {\n      float: right;\n      text-align: right;\n\n      // [mobile -]: Adjust width to 25/75\n      @include break-to-device(mobile) {\n        width: 75%;\n      }\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        text-align: left;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n\n  // Footer title\n  &__title {\n    position: relative;\n    flex-grow: 1;\n    max-width: calc(100% - #{px2rem(48px)});\n    padding: 0 px2rem(20px);\n    font-size: px2rem(18px);\n    line-height: px2rem(48px);\n  }\n\n  // Footer link button\n  &__button {\n    margin: px2rem(4px);\n    padding: px2rem(8px);\n  }\n\n  // Footer link direction (i.e. prev and next)\n  &__direction {\n    position: absolute;\n    right: 0;\n    left: 0;\n    margin-top: px2rem(-20px);\n    padding: 0 px2rem(20px);\n    font-size: px2rem(12.8px);\n    opacity: 0.7;\n  }\n}\n\n// Footer metadata\n.md-footer-meta {\n  background-color: var(--md-footer-bg-color--dark);\n\n  // Footer metadata wrapper\n  &__inner {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    padding: px2rem(4px);\n  }\n\n  // Lighten color for non-hovered text links\n  html &.md-typeset a {\n    color: var(--md-footer-fg-color--light);\n\n    // Text link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-footer-fg-color);\n    }\n  }\n}\n\n// Footer copyright and theme information\n.md-footer-copyright {\n  width: 100%;\n  margin: auto px2rem(12px);\n  padding: px2rem(8px) 0;\n  color: var(--md-footer-fg-color--lighter);\n  font-size: px2rem(12.8px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    width: auto;\n  }\n\n  // Footer copyright highlight - this is the upper part of the copyright and\n  // theme information, which will include a darker color than the theme link\n  &__highlight {\n    color: var(--md-footer-fg-color--light);\n  }\n}\n\n// Footer social links\n.md-footer-social {\n  margin: 0 px2rem(8px);\n  padding: px2rem(4px) 0 px2rem(12px);\n\n  // [tablet portrait +]: Show copyright and social links in one line\n  @include break-from-device(tablet portrait) {\n    padding: px2rem(12px) 0;\n  }\n\n  // Footer social link\n  &__link {\n    display: inline-block;\n    width: px2rem(32px);\n    height: px2rem(32px);\n    text-align: center;\n\n    // Adjust line-height to match height for correct alignment\n    &::before {\n      line-height: 1.9;\n    }\n\n    // Fill icon with current color\n    svg {\n      max-height: px2rem(16px);\n      vertical-align: -25%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-nav-icon--prev: svg-load(\"material/arrow-left.svg\");\n  --md-nav-icon--next: svg-load(\"material/chevron-right.svg\");\n  --md-toc-icon: svg-load(\"material/table-of-contents.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Navigation\n.md-nav {\n  font-size: px2rem(14px);\n  line-height: 1.3;\n\n  // Navigation title\n  &__title {\n    display: block;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    font-weight: 700;\n    text-overflow: ellipsis;\n\n    // Navigaton button\n    .md-nav__button {\n      display: none;\n\n      // Stretch images based on height, as it's the smaller dimension\n      img {\n        width: auto;\n        height: 100%;\n      }\n\n      // Button with logo, pointing to `config.site_url`\n      &.md-logo {\n\n        // Image or icon\n        img,\n        svg {\n          display: block;\n          width: px2rem(48px);\n          height: px2rem(48px);\n          fill: currentColor;\n        }\n      }\n    }\n  }\n\n  // Navigation list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Navigation item\n  &__item {\n    padding: 0 px2rem(12px);\n\n    // Navigation item on level 2\n    & & {\n      padding-right: 0;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(12px);\n        padding-left: 0;\n      }\n    }\n  }\n\n  // Navigation link\n  &__link {\n    display: block;\n    margin-top: 0.625em;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    cursor: pointer;\n    transition: color 125ms;\n    scroll-snap-align: start;\n\n    // Link in blurred state\n    &[data-md-state=\"blur\"] {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Active link\n    .md-nav__item &--active {\n      color: var(--md-typeset-a-color);\n    }\n\n    // Navigation link in nested list\n    .md-nav__item--nested > & {\n      color: inherit;\n    }\n\n    // Navigation link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Navigation link to table of contents\n    .md-nav--primary &[for=\"__toc\"] {\n      display: none;\n\n      // Table of contents icon\n      .md-icon::after {\n        display: block;\n        width: 100%;\n        height: 100%;\n        mask-image: var(--md-toc-icon);\n        background-color: currentColor;\n      }\n\n      // Hide table of contents\n      ~ .md-nav {\n        display: none;\n      }\n    }\n  }\n\n  // Repository information container\n  &__source {\n    display: none;\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Primary and nested navigation\n    &--primary,\n    &--primary & {\n      position: absolute;\n      top: 0;\n      right: 0;\n      left: 0;\n      z-index: 1;\n      display: flex;\n      flex-direction: column;\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n    }\n\n    // Primary navigation\n    &--primary {\n\n      // Navigation title and item\n      .md-nav__title,\n      .md-nav__item {\n        font-size: px2rem(16px);\n        line-height: 1.5;\n      }\n\n      // Navigation title\n      .md-nav__title {\n        position: relative;\n        height: px2rem(112px);\n        padding: px2rem(60px) px2rem(16px) px2rem(4px);\n        color: var(--md-default-fg-color--light);\n        font-weight: 400;\n        line-height: px2rem(48px);\n        white-space: nowrap;\n        background-color: var(--md-default-fg-color--lightest);\n        cursor: pointer;\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: px2rem(8px);\n          left: px2rem(8px);\n          display: block;\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin: px2rem(4px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(8px);\n            left: initial;\n          }\n\n          // Navigation icon in link to previous level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--prev);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n\n        // Navigation list\n        ~ .md-nav__list {\n          overflow-y: auto;\n          background-color: var(--md-default-bg-color);\n          box-shadow:\n            0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;\n          scroll-snap-type: y mandatory;\n          touch-action: pan-y;\n\n          // Omit border on first child\n          > :first-child {\n            border-top: 0;\n          }\n        }\n\n        // Top-level navigation title\n        &[for=\"__drawer\"] {\n          color: var(--md-primary-bg-color);\n          background-color: var(--md-primary-fg-color);\n        }\n\n        // Button with logo, pointing to `config.site_url`\n        .md-logo {\n          position: absolute;\n          top: px2rem(4px);\n          left: px2rem(4px);\n          display: block;\n          margin: px2rem(4px);\n          padding: px2rem(8px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: px2rem(4px);\n            left: initial;\n          }\n        }\n      }\n\n      // Navigation list\n      .md-nav__list {\n        flex: 1;\n      }\n\n      // Navigation item\n      .md-nav__item {\n        padding: 0;\n        border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);\n\n        // Navigation link in nested navigation\n        &--nested > .md-nav__link {\n          padding-right: px2rem(48px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            padding-right: px2rem(16px);\n            padding-left: px2rem(48px);\n          }\n        }\n\n        // Navigation link in active navigation\n        &--active > .md-nav__link {\n          color: var(--md-typeset-a-color);\n\n          // Navigation link on focus/hover\n          &:focus,\n          &:hover {\n            color: var(--md-accent-fg-color);\n          }\n        }\n      }\n\n      // Navigation link\n      .md-nav__link {\n        position: relative;\n        margin-top: 0;\n        padding: px2rem(12px) px2rem(16px);\n\n        // Navigation icon\n        .md-nav__icon {\n          position: absolute;\n          top: 50%;\n          right: px2rem(12px);\n          width: px2rem(24px);\n          height: px2rem(24px);\n          margin-top: px2rem(-12px);\n          color: inherit;\n          font-size: px2rem(24px);\n\n          // Adjust for right-to-left languages\n          [dir=\"rtl\"] & {\n            right: initial;\n            left: px2rem(12px);\n          }\n\n          // Navigation icon in link to next level\n          &::after {\n            display: block;\n            width: 100%;\n            height: 100%;\n            background-color: currentColor;\n            mask-image: var(--md-nav-icon--next);\n            mask-repeat: no-repeat;\n            mask-size: contain;\n            content: \"\";\n          }\n        }\n      }\n\n      // Flip icon vertically\n      .md-nav__icon {\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] &::after {\n          transform: scale(-1);\n        }\n      }\n\n      // Table of contents contained in primary navigation\n      .md-nav--secondary {\n\n        // Navigation link - omit unnecessary layering\n        .md-nav__link {\n          position: static;\n        }\n\n        // Navigation on level 2-6\n        .md-nav {\n          position: static;\n          background-color: transparent;\n\n          // Navigation link on level 3\n          .md-nav__link {\n            padding-left: px2rem(28px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(28px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 4\n          .md-nav .md-nav__link {\n            padding-left: px2rem(40px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(40px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 5\n          .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(52px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(52px);\n              padding-left: initial;\n            }\n          }\n\n          // Navigation link on level 6\n          .md-nav .md-nav .md-nav .md-nav__link {\n            padding-left: px2rem(64px);\n\n            // Adjust for right-to-left languages\n            [dir=\"rtl\"] & {\n              padding-right: px2rem(64px);\n              padding-left: initial;\n            }\n          }\n        }\n      }\n    }\n\n    // Table of contents\n    &--secondary {\n      background-color: transparent;\n    }\n\n    // Toggle for nested navigation\n    &__toggle ~ & {\n      display: flex;\n      transform: translateX(100%);\n      opacity: 0;\n      transition:\n        transform 250ms cubic-bezier(0.8, 0, 0.6, 1),\n        opacity   125ms 50ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        transform: translateX(-100%);\n      }\n    }\n\n    // Show nested navigation when toggle is active\n    &__toggle:checked ~ & {\n      transform: translateX(0);\n      opacity: 1;\n      transition:\n        transform 250ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   125ms 125ms;\n\n      // Navigation list\n      > .md-nav__list {\n        // Hack: promote to own layer to reduce jitter\n        backface-visibility: hidden;\n      }\n    }\n  }\n\n  // [tablet portrait -]: Layered navigation with table of contents\n  @include break-to-device(tablet portrait) {\n\n    // Show link to table of contents\n    &--primary &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n\n    // Repository information container\n    &__source {\n      display: block;\n      padding: 0 px2rem(4px);\n      color: var(--md-primary-bg-color);\n      background-color: var(--md-primary-fg-color--dark);\n    }\n  }\n\n  // [tablet landscape]: Layered navigation with table of contents\n  @include break-at-device(tablet landscape) {\n\n    // Show link to integrated table of contents\n    &--integrated &__link[for=\"__toc\"] {\n      display: block;\n      padding-right: px2rem(48px);\n      scroll-snap-align: initial;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(16px);\n        padding-left: px2rem(48px);\n      }\n\n      // Show table of contents icon\n      .md-icon::after {\n        content: \"\";\n      }\n\n      // Hide navigation link to current page\n      + .md-nav__link {\n        display: none;\n      }\n\n      // Show table of contents\n      ~ .md-nav {\n        display: flex;\n      }\n    }\n  }\n\n  // [tablet landscape +]: Tree-like table of contents\n  @include break-from-device(tablet landscape) {\n\n    // Navigation title\n    &--secondary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__toc\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n  }\n\n  // [screen +]: Tree-like navigation\n  @include break-from-device(screen) {\n    transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);\n\n    // Navigation title\n    &--primary &__title {\n\n      // Adjust snapping behavior\n      &[for=\"__drawer\"] {\n        scroll-snap-align: start;\n      }\n\n      // Hide navigation icon\n      .md-nav__icon {\n        display: none;\n      }\n    }\n\n    // Hide toggle for nested navigation\n    &__toggle ~ & {\n      display: none;\n    }\n\n    // Show nested navigation when toggle is active or indeterminate\n    &__toggle:checked ~ &,\n    &__toggle:indeterminate ~ & {\n      display: block;\n    }\n\n    // Hide navigation title in nested navigation\n    &__item--nested > & > &__title {\n      display: none;\n    }\n\n    // Navigation section\n    &__item--section {\n      display: block;\n      margin: 1.25em 0;\n\n      // Adjust spacing on last child\n      &:last-child {\n        margin-bottom: 0;\n      }\n\n      // Hide navigation link, as sections are always expanded\n      > .md-nav__link {\n        display: none;\n      }\n\n      // Navigation\n      > .md-nav {\n        display: block;\n\n        // Navigation title\n        > .md-nav__title {\n          display: block;\n          padding: 0;\n          pointer-events: none;\n          scroll-snap-align: start;\n        }\n\n        // Adjust spacing on next level item\n        > .md-nav__list > .md-nav__item {\n          padding: 0;\n        }\n      }\n    }\n\n    // Navigation icon\n    &__icon {\n      float: right;\n      width: px2rem(18px);\n      height: px2rem(18px);\n      transition: transform 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n        transform: rotate(180deg);\n      }\n\n      // Navigation icon content\n      &::after {\n        display: inline-block;\n        width: 100%;\n        height: 100%;\n        vertical-align: px2rem(-2px);\n        background-color: currentColor;\n        mask-image: var(--md-nav-icon--next);\n        mask-repeat: no-repeat;\n        mask-size: contain;\n        content: \"\";\n      }\n\n      // Navigation icon - rotate icon when toggle is active or indeterminate\n      .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,\n      .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {\n        transform: rotate(90deg);\n      }\n    }\n\n    // Modifier for when navigation tabs are rendered\n    &--lifted {\n\n      // Hide nested items on level 1 and site title\n      > .md-nav__list > .md-nav__item--nested,\n      > .md-nav__title {\n        display: none;\n      }\n\n      // Hide level 1 items\n      > .md-nav__list > .md-nav__item {\n        display: none;\n\n        // Active parent navigation item\n        &--active {\n          display: block;\n          padding: 0;\n\n          // Hide nested links\n          > .md-nav__link {\n            display: none;\n          }\n\n          // Show title and adjust spacing\n          > .md-nav > .md-nav__title {\n            display: block;\n            padding: 0 px2rem(12px);\n            pointer-events: none;\n            scroll-snap-align: start;\n          }\n        }\n\n        // Adjust spacing for navigation item on level 2\n        > .md-nav__item {\n          padding-right: px2rem(12px);\n        }\n      }\n\n      // Hack: Always show active navigation tab on breakpoint screen, despite\n      // of checkbox being checked or not. Fixes #1655.\n      .md-nav[data-md-level=\"1\"] {\n        display: block;\n      }\n    }\n\n    // Modifier for when table of contents is rendered in primary navigation\n    &--integrated &__link[for=\"__toc\"] ~ .md-nav {\n      display: block;\n      margin-bottom: 1.25em;\n      border-left: px2rem(1px) solid var(--md-primary-fg-color);\n\n      // Hide navigation title\n      > .md-nav__title {\n        display: none;\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-search-result-icon: svg-load(\"material/file-search-outline.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Search\n.md-search {\n  position: relative;\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n    padding: px2rem(4px) 0;\n  }\n\n  // [no-js]: Hide search\n  .no-js & {\n    display: none;\n  }\n\n  // Search overlay\n  &__overlay {\n    z-index: 1;\n    opacity: 0;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: absolute;\n      top: px2rem(4px);\n      left: px2rem(-44px);\n      width: px2rem(40px);\n      height: px2rem(40px);\n      overflow: hidden;\n      background-color: var(--md-default-bg-color);\n      border-radius: px2rem(20px);\n      transform-origin: center;\n      transition:\n        transform 300ms 100ms,\n        opacity   200ms 200ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-44px);\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        opacity: 1;\n        transition:\n          transform 400ms,\n          opacity   100ms;\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 0;\n      height: 0;\n      background-color: hsla(0, 0%, 0%, 0.54);\n      cursor: pointer;\n      transition:\n        width     0ms 250ms,\n        height    0ms 250ms,\n        opacity 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: 0;\n        left: initial;\n      }\n\n      // Show overlay when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        width: 100%;\n        // Hack: when the header is translated upon scrolling, a new layer is\n        // induced, which means that the height will now refer to the height of\n        // the header, albeit positioning is fixed. This should be mitigated\n        // in all cases when setting the height to 2x the viewport.\n        height: 200vh;\n        opacity: 1;\n        transition:\n          width     0ms,\n          height    0ms,\n          opacity 250ms;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [mobile portrait -]: Scale up 45 times\n      @include break-to-device(mobile portrait) {\n        transform: scale(45);\n      }\n\n      // [mobile landscape]: Scale up 60 times\n      @include break-at-device(mobile landscape) {\n        transform: scale(60);\n      }\n\n      // [tablet portrait]: Scale up 75 times\n      @include break-at-device(tablet portrait) {\n        transform: scale(75);\n      }\n    }\n  }\n\n  // Search wrapper\n  &__inner {\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      position: fixed;\n      top: 0;\n      left: 100%;\n      z-index: 2;\n      width: 100%;\n      height: 100%;\n      transform: translateX(5%);\n      opacity: 0;\n      transition:\n        right       0ms 300ms,\n        left        0ms 300ms,\n        transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),\n        opacity   150ms 150ms;\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        left: 0;\n        transform: translateX(0);\n        opacity: 1;\n        transition:\n          right       0ms   0ms,\n          left        0ms   0ms,\n          transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n          opacity   150ms 150ms;\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: 0;\n          left: initial;\n        }\n      }\n\n      // Adjust for right-to-left languages\n      html [dir=\"rtl\"] & {\n        right: 100%;\n        left: initial;\n        transform: translateX(-5%);\n      }\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      position: relative;\n      float: right;\n      width: px2rem(234px);\n      padding: px2rem(2px) 0;\n      transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: left;\n      }\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n\n      // [tablet landscape]: Omit overlaying header title\n      @include break-at-device(tablet landscape) {\n        width: px2rem(468px);\n      }\n\n      // [screen +]: Match width of content area\n      @include break-from-device(screen) {\n        width: px2rem(688px);\n      }\n    }\n  }\n\n  // Search form\n  &__form {\n    position: relative;\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      border-radius: px2rem(2px);\n    }\n  }\n\n  // Search input\n  &__input {\n    position: relative;\n    z-index: 2;\n    padding: 0 px2rem(44px) 0 px2rem(72px);\n    text-overflow: ellipsis;\n    background-color: var(--md-default-bg-color);\n    box-shadow: 0 0 px2rem(12px) transparent;\n    transition:\n      color            250ms,\n      background-color 250ms,\n      box-shadow       250ms;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: 0 px2rem(72px) 0 px2rem(44px);\n    }\n\n    // Search placeholder\n    &::placeholder {\n      transition: color 250ms;\n    }\n\n    // Search icon and placeholder\n    ~ .md-search__icon,\n    &::placeholder {\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Remove the \"x\" rendered by Internet Explorer\n    &::-ms-clear {\n      display: none;\n    }\n\n    // Adjust appearance when search is active\n    [data-md-toggle=\"search\"]:checked ~ .md-header & {\n      box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);\n    }\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      width: 100%;\n      height: px2rem(48px);\n      font-size: px2rem(18px);\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      width: 100%;\n      height: px2rem(36px);\n      padding-left: px2rem(44px);\n      color: inherit;\n      font-size: px2rem(16px);\n      background-color: hsla(0, 0%, 0%, 0.26);\n      border-radius: px2rem(2px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n      }\n\n      // Search icon\n      + .md-search__icon {\n        color: var(--md-primary-bg-color);\n      }\n\n      // Search placeholder\n      &::placeholder {\n        color: var(--md-primary-bg-color--light);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.12);\n      }\n\n      // Adjust appearance when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        color: var(--md-default-fg-color);\n        text-overflow: clip;\n        background-color: var(--md-default-bg-color);\n        border-radius: px2rem(2px) px2rem(2px) 0 0;\n\n        // Search icon and placeholder\n        + .md-search__icon,\n        &::placeholder {\n          color: var(--md-default-fg-color--light);\n        }\n      }\n    }\n  }\n\n  // Search icon\n  &__icon {\n    position: absolute;\n    z-index: 2;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    cursor: pointer;\n    transition:\n      color   250ms,\n      opacity 250ms;\n\n    // Search icon on hover\n    &:hover {\n      opacity: 0.7;\n    }\n\n    // Search focus button\n    &[for=\"__search\"] {\n      top: px2rem(6px);\n      left: px2rem(10px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(10px);\n        left: initial;\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: px2rem(16px);\n          left: initial;\n        }\n\n        // Hide the magnifying glass\n        svg:first-child {\n          display: none;\n        }\n      }\n\n      // [tablet landscape +]: Header-embedded search\n      @include break-from-device(tablet landscape) {\n        pointer-events: none;\n\n        // Hide the back arrow\n        svg:last-child {\n          display: none;\n        }\n      }\n    }\n\n    // Search reset button\n    &[type=\"reset\"] {\n      top: px2rem(6px);\n      right: px2rem(10px);\n      transform: scale(0.75);\n      opacity: 0;\n      transition:\n        transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity   150ms;\n      pointer-events: none;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(10px);\n      }\n\n      // [tablet portrait -]: Search modal\n      @include break-to-device(tablet portrait) {\n        top: px2rem(12px);\n        right: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          right: initial;\n          left: px2rem(16px);\n        }\n      }\n\n      // Show reset button when search is active and input non-empty\n      [data-md-toggle=\"search\"]:checked ~ .md-header\n      .md-search__input:valid ~ & {\n        transform: scale(1);\n        opacity: 1;\n        pointer-events: initial;\n\n        // Search focus icon\n        &:hover {\n          opacity: 0.7;\n        }\n      }\n    }\n  }\n\n  // Search output\n  &__output {\n    position: absolute;\n    z-index: 1;\n    width: 100%;\n    overflow: hidden;\n    border-radius: 0 0 px2rem(2px) px2rem(2px);\n\n    // [tablet portrait -]: Search modal\n    @include break-to-device(tablet portrait) {\n      top: px2rem(48px);\n      bottom: 0;\n    }\n\n    // [tablet landscape +]: Header-embedded search\n    @include break-from-device(tablet landscape) {\n      top: px2rem(38px);\n      opacity: 0;\n      transition: opacity 400ms;\n\n      // Show output when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        @include z-depth(6);\n\n        opacity: 1;\n      }\n    }\n  }\n\n  // Search scroll wrapper\n  &__scrollwrap {\n    height: 100%;\n    overflow-y: auto;\n    background-color: var(--md-default-bg-color);\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping\n    // seems to be something that is not ready for prime time on some browsers.\n    // scroll-snap-type: y mandatory;\n    touch-action: pan-y;\n\n    // Mitigiate excessive repaints on non-retina devices\n    @media (max-resolution: 1dppx) {\n      transform: translateZ(0);\n    }\n\n    // [tablet landscape]: Set fixed width to omit unnecessary reflow\n    @include break-at-device(tablet landscape) {\n      width: px2rem(468px);\n    }\n\n    // [screen +]: Set fixed width to omit unnecessary reflow\n    @include break-from-device(screen) {\n      width: px2rem(688px);\n    }\n\n    // [tablet landscape +]: Limit height to viewport\n    @include break-from-device(tablet landscape) {\n      max-height: 0;\n      scrollbar-width: thin;\n      scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n      // Show scroll wrapper when search is active\n      [data-md-toggle=\"search\"]:checked ~ .md-header & {\n        max-height: 75vh;\n      }\n\n      // Search scroll wrapper on hover\n      &:hover {\n        scrollbar-color: var(--md-accent-fg-color) transparent;\n      }\n\n      // Webkit scrollbar\n      &::-webkit-scrollbar {\n        width: px2rem(4px);\n        height: px2rem(4px);\n      }\n\n      // Webkit scrollbar thumb\n      &::-webkit-scrollbar-thumb {\n        background-color: var(--md-default-fg-color--lighter);\n\n        // Webkit scrollbar thumb on hover\n        &:hover {\n          background-color: var(--md-accent-fg-color);\n        }\n      }\n    }\n  }\n}\n\n// Search result\n.md-search-result {\n  color: var(--md-default-fg-color);\n  word-break: break-word;\n\n  // Search result metadata\n  &__meta {\n    padding: 0 px2rem(16px);\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: px2rem(36px);\n    background-color: var(--md-default-fg-color--lightest);\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: initial;\n      }\n    }\n  }\n\n  // Search result list\n  &__list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  // Search result item\n  &__item {\n    box-shadow: 0 px2rem(-1px) 0 var(--md-default-fg-color--lightest);\n\n    // Omit border on first child\n    &:first-child {\n      box-shadow: none;\n    }\n  }\n\n  // Search result link\n  &__link {\n    display: block;\n    outline: none;\n    transition: background-color 250ms;\n    scroll-snap-align: start;\n\n    // Search result link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Adjust spacing on last child of last link\n    &:last-child p:last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Search result more link\n  &__more summary {\n    display: block;\n    padding: px2em(12px) px2rem(16px);\n    color: var(--md-typeset-a-color);\n    font-size: px2rem(12.8px);\n    outline: 0;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result more link on focus/hover\n    &:focus,\n    &:hover {\n      color: var(--md-accent-fg-color);\n      background-color: var(--md-accent-fg-color--transparent);\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n\n    // Adjust transparency of less relevant results\n    ~ * > * {\n      opacity: 0.65;\n    }\n  }\n\n  // Search result article\n  &__article {\n    position: relative;\n    padding: 0 px2rem(16px);\n    overflow: hidden;\n\n    // [tablet landscape +]: Adjust spacing\n    @include break-from-device(tablet landscape) {\n      padding-left: px2rem(44px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        padding-right: px2rem(44px);\n        padding-left: px2rem(16px);\n      }\n    }\n\n    // Search result article document\n    &--document {\n\n      // Search result title\n      .md-search-result__title {\n        margin: px2rem(11px) 0;\n        font-weight: 400;\n        font-size: px2rem(16px);\n        line-height: 1.4;\n      }\n    }\n  }\n\n  // Search result icon\n  &__icon {\n    position: absolute;\n    left: 0;\n    width: px2rem(24px);\n    height: px2rem(24px);\n    margin: px2rem(10px);\n    color: var(--md-default-fg-color--light);\n\n    // [tablet portrait -]: Hide icon\n    @include break-to-device(tablet portrait) {\n      display: none;\n    }\n\n    // Search result icon content\n    &::after {\n      display: inline-block;\n      width: 100%;\n      height: 100%;\n      background-color: currentColor;\n      mask-image: var(--md-search-result-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: 0;\n      left: initial;\n\n      // Flip icon vertically\n      &::after {\n        transform: scaleX(-1);\n      }\n    }\n  }\n\n  // Search result title\n  &__title {\n    margin: 0.5em 0;\n    font-weight: 700;\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n  }\n\n  // Search result teaser\n  &__teaser {\n    display: -webkit-box;\n    max-height: px2rem(40px);\n    margin: 0.5em 0;\n    overflow: hidden;\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n    line-height: 1.6;\n    text-overflow: ellipsis;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 2;\n\n    // [mobile -]: Adjust number of lines\n    @include break-to-device(mobile) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // [tablet landscape]: Adjust number of lines\n    @include break-at-device(tablet landscape) {\n      max-height: px2rem(60px);\n      -webkit-line-clamp: 3;\n    }\n\n    // Search term highlighting\n    mark {\n      text-decoration: underline;\n      background-color: transparent;\n    }\n  }\n\n  // Search result terms\n  &__terms {\n    margin: 0.5em 0;\n    font-size: px2rem(12.8px);\n    font-style: italic;\n  }\n\n  // Search term highlighting\n  mark {\n    color: var(--md-accent-fg-color);\n    background-color: transparent;\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Selection\n.md-select {\n  position: relative;\n  z-index: 1;\n\n  // Selection bubble\n  &__inner {\n    position: absolute;\n    top: calc(100% - #{px2rem(4px)});\n    left: 50%;\n    max-height: 0;\n    margin-top: px2rem(4px);\n    color: var(--md-default-fg-color);\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    transform: translate3d(-50%, px2rem(6px), 0);\n    opacity: 0;\n    transition:\n      transform  250ms 375ms,\n      opacity    250ms 250ms,\n      max-height   0ms 500ms;\n\n    // Selection bubble on parent focus/hover\n    .md-select:focus-within &,\n    .md-select:hover & {\n      max-height: px2rem(200px);\n      transform: translate3d(-50%, 0, 0);\n      opacity: 1;\n      transition:\n        transform  250ms cubic-bezier(0.1, 0.7, 0.1, 1),\n        opacity    250ms,\n        max-height 250ms;\n    }\n\n    // Selection bubble handle\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 50%;\n      width: 0;\n      height: 0;\n      margin-top: px2rem(-4px);\n      margin-left: px2rem(-4px);\n      border: px2rem(4px) solid transparent;\n      border-top: 0;\n      border-bottom-color: var(--md-default-bg-color);\n      content: \"\";\n    }\n  }\n\n  // Selection list\n  &__list {\n    max-height: inherit;\n    margin: 0;\n    padding: 0;\n    overflow: auto;\n    font-size: px2rem(16px);\n    list-style-type: none;\n    border-radius: px2rem(2px);\n  }\n\n  // Selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    cursor: pointer;\n    transition:\n      background-color 250ms,\n      color            250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Sidebar\n.md-sidebar {\n  position: sticky;\n  top: px2rem(48px);\n  flex-shrink: 0;\n  align-self: flex-start;\n  width: px2rem(242px);\n  padding: px2rem(24px) 0;\n\n  // [print]: Hide sidebar\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Show navigation as drawer\n  @include break-to-device(tablet) {\n\n    // Primary sidebar with navigation\n    &--primary {\n      position: fixed;\n      top: 0;\n      left: px2rem(-242px);\n      z-index: 3;\n      display: block;\n      width: px2rem(242px);\n      height: 100%;\n      background-color: var(--md-default-bg-color);\n      transform: translateX(0);\n      transition:\n        transform  250ms cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 250ms;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(-242px);\n        left: initial;\n      }\n\n      // Show sidebar when drawer is active\n      [data-md-toggle=\"drawer\"]:checked ~ .md-container & {\n        @include z-depth(8);\n\n        transform: translateX(px2rem(242px));\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          transform: translateX(px2rem(-242px));\n        }\n      }\n\n      // Stretch scroll wrapper for primary sidebar\n      .md-sidebar__scrollwrap {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        margin: 0;\n        scroll-snap-type: none;\n        overflow: hidden;\n      }\n    }\n  }\n\n  // [screen +]: Show navigation as sidebar\n  @include break-from-device(screen) {\n    height: 0;\n\n    // [no-js]: Switch to native sticky behavior\n    .no-js & {\n      height: auto;\n    }\n  }\n\n  // Secondary sidebar with table of contents\n  &--secondary {\n    display: none;\n    order: 2;\n\n    // [tablet landscape +]: Show table of contents as sidebar\n    @include break-from-device(tablet landscape) {\n      height: 0;\n\n      // [no-js]: Switch to native sticky behavior\n      .no-js & {\n        height: auto;\n      }\n\n      // Sidebar is visible\n      &:not([hidden]) {\n        display: block;\n      }\n\n      // Ensure smooth scrolling on iOS\n      .md-sidebar__scrollwrap {\n        touch-action: pan-y;\n      }\n    }\n  }\n\n  // Sidebar scroll wrapper\n  &__scrollwrap {\n    margin: 0 px2rem(4px);\n    overflow-y: auto;\n    // Hack: promote to own layer to reduce jitter\n    backface-visibility: hidden;\n    // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container\n    // to the bottom if `scroll-snap-type` is set on the initial render. For\n    // this reason, we disable scroll snapping until this is resolved (#1667).\n    // scroll-snap-type: y mandatory;\n    scrollbar-width: thin;\n    scrollbar-color: var(--md-default-fg-color--lighter) transparent;\n\n    // Sidebar scroll wrapper on hover\n    &:hover {\n      scrollbar-color: var(--md-accent-fg-color) transparent;\n    }\n\n    // Webkit scrollbar\n    &::-webkit-scrollbar {\n      width: px2rem(4px);\n      height: px2rem(4px);\n    }\n\n    // Webkit scrollbar thumb\n    &::-webkit-scrollbar-thumb {\n      background-color: var(--md-default-fg-color--lighter);\n\n      // Webkit scrollbar thumb on hover\n      &:hover {\n        background-color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n\n// [tablet -]: Show overlay on active drawer\n@include break-to-device(tablet) {\n\n  // Sidebar overlay\n  .md-overlay {\n    position: fixed;\n    top: 0;\n    z-index: 3;\n    width: 0;\n    height: 0;\n    background-color: hsla(0, 0%, 0%, 0.54);\n    opacity: 0;\n    transition:\n      width     0ms 250ms,\n      height    0ms 250ms,\n      opacity 250ms;\n\n    // Show overlay when drawer is active\n    [data-md-toggle=\"drawer\"]:checked ~ & {\n      width: 100%;\n      height: 100%;\n      opacity: 1;\n      transition:\n        width     0ms,\n        height    0ms,\n        opacity 250ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Navigation tabs\n.md-tabs {\n  width: 100%;\n  overflow: auto;\n  color: var(--md-primary-bg-color);\n  background-color: var(--md-primary-fg-color);\n  transition: background-color 250ms;\n\n  // [print]: Hide tabs\n  @media print {\n    display: none;\n  }\n\n  // [tablet -]: Hide tabs\n  @include break-to-device(tablet) {\n    display: none;\n  }\n\n  // Tabs in hidden state, i.e. when scrolling down\n  &[data-md-state=\"hidden\"] {\n    pointer-events: none;\n  }\n\n  // Navigation tabs list\n  &__list {\n    margin: 0;\n    margin-left: px2rem(4px);\n    padding: 0;\n    white-space: nowrap;\n    list-style: none;\n    contain: content;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(4px);\n      margin-left: initial;\n    }\n  }\n\n  // Navigation tabs item\n  &__item {\n    display: inline-block;\n    height: px2rem(48px);\n    padding-right: px2rem(12px);\n    padding-left: px2rem(12px);\n  }\n\n  // Navigation tabs link - could be defined as block elements and aligned via\n  // line height, but this would imply more repaints when scrolling\n  &__link {\n    display: block;\n    margin-top: px2rem(16px);\n    font-size: px2rem(14px);\n    // Hack: save a repaint when tabs are appearing on scrolling up\n    backface-visibility: hidden;\n    opacity: 0.7;\n    transition:\n      transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),\n      opacity   250ms;\n\n    // Active link and link on focus/hover\n    &--active,\n    &:focus,\n    &:hover {\n      color: inherit;\n      opacity: 1;\n    }\n\n    // Delay transitions by a small amount\n    @for $i from 2 through 16 {\n      .md-tabs__item:nth-child(#{$i}) & {\n        transition-delay: 20ms * ($i - 1);\n      }\n    }\n\n    // Hide tabs upon scrolling - disable transition to minimizes repaints\n    // while scrolling down, while scrolling up seems to be okay\n    .md-tabs[data-md-state=\"hidden\"] & {\n      transform: translateY(50%);\n      opacity: 0;\n      transition:\n        transform 0ms 100ms,\n        opacity 100ms;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-version-icon: svg-load(\"fontawesome/solid/caret-down.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Version selection\n.md-version {\n  flex-shrink: 0;\n  height: px2rem(48px);\n  font-size: px2rem(16px);\n\n  // Current selection\n  &__current {\n    position: relative;\n    // Hack: in general, we would use `vertical-align` to align the version at\n    // the bottom with the title, but since the list uses absolute positioning,\n    // this won't work consistently. Furthermore, we would need to use inline\n    // positioning to align the links, which looks jagged.\n    top: px2rem(1px);\n    margin-right: px2rem(8px);\n    margin-left: px2rem(28px);\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(28px);\n      margin-left: px2rem(8px);\n    }\n\n    // Version selection icon\n    &::after {\n      display: inline-block;\n      width: px2rem(8px);\n      height: px2rem(12px);\n      margin-left: px2rem(8px);\n      background-color: currentColor;\n      mask-image: var(--md-version-icon);\n      mask-repeat: no-repeat;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        margin-right: px2rem(8px);\n        margin-left: initial;\n      }\n    }\n  }\n\n  // Version selection list\n  &__list {\n    position: absolute;\n    top: px2rem(3px);\n    z-index: 1;\n    max-height: px2rem(36px);\n    margin: px2rem(4px) px2rem(16px);\n    padding: 0;\n    overflow: auto;\n    color: var(--md-default-fg-color);\n    list-style-type: none;\n    background-color: var(--md-default-bg-color);\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),\n      0 0           px2rem(1px)  hsla(0, 0%, 0%, 0.25);\n    opacity: 0;\n    transition:\n      max-height 0ms 500ms,\n      opacity  250ms 250ms;\n    scroll-snap-type: y mandatory;\n\n    // List on focus/hover\n    &:focus-within,\n    &:hover {\n      max-height: px2rem(200px);\n      opacity: 1;\n      transition:\n        max-height 250ms,\n        opacity    250ms;\n    }\n  }\n\n  // Version selection item\n  &__item {\n    line-height: px2rem(36px);\n  }\n\n  // Version selection link\n  &__link {\n    display: block;\n    width: 100%;\n    padding-right: px2rem(24px);\n    padding-left: px2rem(12px);\n    white-space: nowrap;\n    cursor: pointer;\n    transition:\n      color            250ms,\n      background-color 250ms;\n    scroll-snap-align: start;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding-right: px2rem(12px);\n      padding-left: px2rem(24px);\n    }\n\n    // Link on focus/hover\n    &:focus,\n    &:hover {\n      background-color: var(--md-default-fg-color--lightest);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n/// Admonition flavours\n$admonitions: (\n  note:                       pencil $clr-blue-a200,\n  abstract summary tldr:      text-subject $clr-light-blue-a400,\n  info todo:                  information $clr-cyan-a700,\n  tip hint important:         fire $clr-teal-a700,\n  success check done:         check-circle $clr-green-a700,\n  question help faq:          help-circle $clr-light-green-a700,\n  warning caution attention:  alert $clr-orange-a400,\n  failure fail missing:       close-circle $clr-red-a200,\n  danger error:               flash-circle $clr-red-a400,\n  bug:                        bug $clr-pink-a400,\n  example:                    format-list-numbered $clr-deep-purple-a200,\n  quote cite:                 format-quote-close $clr-grey\n) !default;\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  @each $names, $props in $admonitions {\n    --md-admonition-icon--#{nth($names, 1)}:\n      svg-load(\"material/#{nth($props, 1)}.svg\");\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Admonition\n  .admonition {\n    margin: px2em(20px, 12.8px) 0;\n    padding: 0 px2rem(12px);\n    overflow: hidden;\n    color: var(--md-admonition-fg-color);\n    font-size: px2rem(12.8px);\n    page-break-inside: avoid;\n    background-color: var(--md-admonition-bg-color);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n    border-radius: px2rem(2px);\n    box-shadow:\n      0 px2rem(4px)   px2rem(10px) hsla(0, 0%, 0%, 0.05),\n      0 px2rem(0.5px) px2rem(1px)  hsla(0, 0%, 0%, 0.05);\n\n    // [print]: Omit shadow as it may lead to rendering errors\n    @media print {\n      box-shadow: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for nested admonitions\n    .admonition {\n      margin: 1em 0;\n    }\n\n    // Adjust spacing for contained table wrappers\n    .md-typeset__scrollwrap {\n      margin: 1em px2rem(-12px);\n    }\n\n    // Adjust spacing for contained tables\n    .md-typeset__table {\n      padding: 0 px2rem(12px);\n    }\n\n    // Adjust spacing for single-child tabbed block container\n    > .tabbed-set:only-child {\n      margin-top: 0;\n    }\n\n    // Adjust spacing on last child\n    html & > :last-child {\n      margin-bottom: px2rem(12px);\n    }\n  }\n\n  // Admonition title\n  .admonition-title {\n    position: relative;\n    margin: 0 px2rem(-12px) 0 px2rem(-16px);\n    padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);\n    font-weight: 700;\n    background-color: transparentize($clr-blue-a200, 0.9);\n    border-left: px2rem(4px) solid $clr-blue-a200;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin: 0 px2rem(-16px) 0 px2rem(-12px);\n      padding: px2rem(8px) px2rem(40px) px2rem(8px) px2rem(12px);\n      border-right: px2rem(4px) solid $clr-blue-a200;\n      border-left: none;\n    }\n\n    // Adjust spacing for title-only admonitions\n    html &:last-child {\n      margin-bottom: 0;\n    }\n\n    // Admonition icon\n    &::before {\n      position: absolute;\n      left: px2rem(12px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: $clr-blue-a200;\n      mask-image: var(--md-admonition-icon--note);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: px2rem(12px);\n        left: initial;\n      }\n    }\n\n    // Omit background on inline code blocks, as they don't go well with the\n    // pastelly tones applied to admonition titles\n    code {\n      margin: initial;\n      padding: initial;\n      color: currentColor;\n      background-color: transparent;\n      border-radius: initial;\n      box-shadow: none;\n    }\n\n    // Adjust spacing on last tabbed block container child - if the tabbed\n    // block container is the sole child, it looks better to omit the margin\n    + .tabbed-set:last-child {\n      margin-top: 0;\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: flavours\n// ----------------------------------------------------------------------------\n\n@each $names, $props in $admonitions {\n  $name: nth($names, 1);\n  $tint: nth($props, 2);\n\n  // Admonition flavour\n  .md-typeset .admonition.#{$name} {\n    border-color: $tint;\n  }\n\n  // Admonition flavour title\n  .md-typeset .#{$name} > .admonition-title {\n    background-color: transparentize($tint, 0.9);\n    border-color: $tint;\n\n    // Admonition icon\n    &::before {\n      background-color: $tint;\n      mask-image: var(--md-admonition-icon--#{$name});\n      mask-repeat: no-repeat;\n      mask-size: contain;\n    }\n  }\n\n  // Define synonyms for flavours\n  @if length($names) > 1 {\n    @for $n from 2 through length($names) {\n      .#{nth($names, $n)} {\n        @extend .#{$name};\n      }\n    }\n  }\n}\n","// ==========================================================================\n//\n// Name:        UI Color Palette\n// Description: The color palette of material design.\n// Version:     2.3.1\n//\n// Author:      Denis Malinochkin\n// Git:         https://github.com/mrmlnc/material-color\n//\n// twitter:     @mrmlnc\n//\n// ==========================================================================\n\n\n//\n// List of base colors\n//\n\n// $clr-red\n// $clr-pink\n// $clr-purple\n// $clr-deep-purple\n// $clr-indigo\n// $clr-blue\n// $clr-light-blue\n// $clr-cyan\n// $clr-teal\n// $clr-green\n// $clr-light-green\n// $clr-lime\n// $clr-yellow\n// $clr-amber\n// $clr-orange\n// $clr-deep-orange\n// $clr-brown\n// $clr-grey\n// $clr-blue-grey\n// $clr-black\n// $clr-white\n\n\n//\n// Red\n//\n\n$clr-red-list: (\n  \"base\": #f44336,\n  \"50\":   #ffebee,\n  \"100\":  #ffcdd2,\n  \"200\":  #ef9a9a,\n  \"300\":  #e57373,\n  \"400\":  #ef5350,\n  \"500\":  #f44336,\n  \"600\":  #e53935,\n  \"700\":  #d32f2f,\n  \"800\":  #c62828,\n  \"900\":  #b71c1c,\n  \"a100\": #ff8a80,\n  \"a200\": #ff5252,\n  \"a400\": #ff1744,\n  \"a700\": #d50000\n);\n\n$clr-red:      map-get($clr-red-list, \"base\");\n\n$clr-red-50:   map-get($clr-red-list, \"50\");\n$clr-red-100:  map-get($clr-red-list, \"100\");\n$clr-red-200:  map-get($clr-red-list, \"200\");\n$clr-red-300:  map-get($clr-red-list, \"300\");\n$clr-red-400:  map-get($clr-red-list, \"400\");\n$clr-red-500:  map-get($clr-red-list, \"500\");\n$clr-red-600:  map-get($clr-red-list, \"600\");\n$clr-red-700:  map-get($clr-red-list, \"700\");\n$clr-red-800:  map-get($clr-red-list, \"800\");\n$clr-red-900:  map-get($clr-red-list, \"900\");\n$clr-red-a100: map-get($clr-red-list, \"a100\");\n$clr-red-a200: map-get($clr-red-list, \"a200\");\n$clr-red-a400: map-get($clr-red-list, \"a400\");\n$clr-red-a700: map-get($clr-red-list, \"a700\");\n\n\n//\n// Pink\n//\n\n$clr-pink-list: (\n  \"base\": #e91e63,\n  \"50\":   #fce4ec,\n  \"100\":  #f8bbd0,\n  \"200\":  #f48fb1,\n  \"300\":  #f06292,\n  \"400\":  #ec407a,\n  \"500\":  #e91e63,\n  \"600\":  #d81b60,\n  \"700\":  #c2185b,\n  \"800\":  #ad1457,\n  \"900\":  #880e4f,\n  \"a100\": #ff80ab,\n  \"a200\": #ff4081,\n  \"a400\": #f50057,\n  \"a700\": #c51162\n);\n\n$clr-pink:      map-get($clr-pink-list, \"base\");\n\n$clr-pink-50:   map-get($clr-pink-list, \"50\");\n$clr-pink-100:  map-get($clr-pink-list, \"100\");\n$clr-pink-200:  map-get($clr-pink-list, \"200\");\n$clr-pink-300:  map-get($clr-pink-list, \"300\");\n$clr-pink-400:  map-get($clr-pink-list, \"400\");\n$clr-pink-500:  map-get($clr-pink-list, \"500\");\n$clr-pink-600:  map-get($clr-pink-list, \"600\");\n$clr-pink-700:  map-get($clr-pink-list, \"700\");\n$clr-pink-800:  map-get($clr-pink-list, \"800\");\n$clr-pink-900:  map-get($clr-pink-list, \"900\");\n$clr-pink-a100: map-get($clr-pink-list, \"a100\");\n$clr-pink-a200: map-get($clr-pink-list, \"a200\");\n$clr-pink-a400: map-get($clr-pink-list, \"a400\");\n$clr-pink-a700: map-get($clr-pink-list, \"a700\");\n\n\n//\n// Purple\n//\n\n$clr-purple-list: (\n  \"base\": #9c27b0,\n  \"50\":   #f3e5f5,\n  \"100\":  #e1bee7,\n  \"200\":  #ce93d8,\n  \"300\":  #ba68c8,\n  \"400\":  #ab47bc,\n  \"500\":  #9c27b0,\n  \"600\":  #8e24aa,\n  \"700\":  #7b1fa2,\n  \"800\":  #6a1b9a,\n  \"900\":  #4a148c,\n  \"a100\": #ea80fc,\n  \"a200\": #e040fb,\n  \"a400\": #d500f9,\n  \"a700\": #aa00ff\n);\n\n$clr-purple:      map-get($clr-purple-list, \"base\");\n\n$clr-purple-50:   map-get($clr-purple-list, \"50\");\n$clr-purple-100:  map-get($clr-purple-list, \"100\");\n$clr-purple-200:  map-get($clr-purple-list, \"200\");\n$clr-purple-300:  map-get($clr-purple-list, \"300\");\n$clr-purple-400:  map-get($clr-purple-list, \"400\");\n$clr-purple-500:  map-get($clr-purple-list, \"500\");\n$clr-purple-600:  map-get($clr-purple-list, \"600\");\n$clr-purple-700:  map-get($clr-purple-list, \"700\");\n$clr-purple-800:  map-get($clr-purple-list, \"800\");\n$clr-purple-900:  map-get($clr-purple-list, \"900\");\n$clr-purple-a100: map-get($clr-purple-list, \"a100\");\n$clr-purple-a200: map-get($clr-purple-list, \"a200\");\n$clr-purple-a400: map-get($clr-purple-list, \"a400\");\n$clr-purple-a700: map-get($clr-purple-list, \"a700\");\n\n\n//\n// Deep purple\n//\n\n$clr-deep-purple-list: (\n  \"base\": #673ab7,\n  \"50\":   #ede7f6,\n  \"100\":  #d1c4e9,\n  \"200\":  #b39ddb,\n  \"300\":  #9575cd,\n  \"400\":  #7e57c2,\n  \"500\":  #673ab7,\n  \"600\":  #5e35b1,\n  \"700\":  #512da8,\n  \"800\":  #4527a0,\n  \"900\":  #311b92,\n  \"a100\": #b388ff,\n  \"a200\": #7c4dff,\n  \"a400\": #651fff,\n  \"a700\": #6200ea\n);\n\n$clr-deep-purple:      map-get($clr-deep-purple-list, \"base\");\n\n$clr-deep-purple-50:   map-get($clr-deep-purple-list, \"50\");\n$clr-deep-purple-100:  map-get($clr-deep-purple-list, \"100\");\n$clr-deep-purple-200:  map-get($clr-deep-purple-list, \"200\");\n$clr-deep-purple-300:  map-get($clr-deep-purple-list, \"300\");\n$clr-deep-purple-400:  map-get($clr-deep-purple-list, \"400\");\n$clr-deep-purple-500:  map-get($clr-deep-purple-list, \"500\");\n$clr-deep-purple-600:  map-get($clr-deep-purple-list, \"600\");\n$clr-deep-purple-700:  map-get($clr-deep-purple-list, \"700\");\n$clr-deep-purple-800:  map-get($clr-deep-purple-list, \"800\");\n$clr-deep-purple-900:  map-get($clr-deep-purple-list, \"900\");\n$clr-deep-purple-a100: map-get($clr-deep-purple-list, \"a100\");\n$clr-deep-purple-a200: map-get($clr-deep-purple-list, \"a200\");\n$clr-deep-purple-a400: map-get($clr-deep-purple-list, \"a400\");\n$clr-deep-purple-a700: map-get($clr-deep-purple-list, \"a700\");\n\n\n//\n// Indigo\n//\n\n$clr-indigo-list: (\n  \"base\": #3f51b5,\n  \"50\":   #e8eaf6,\n  \"100\":  #c5cae9,\n  \"200\":  #9fa8da,\n  \"300\":  #7986cb,\n  \"400\":  #5c6bc0,\n  \"500\":  #3f51b5,\n  \"600\":  #3949ab,\n  \"700\":  #303f9f,\n  \"800\":  #283593,\n  \"900\":  #1a237e,\n  \"a100\": #8c9eff,\n  \"a200\": #536dfe,\n  \"a400\": #3d5afe,\n  \"a700\": #304ffe\n);\n\n$clr-indigo:      map-get($clr-indigo-list, \"base\");\n\n$clr-indigo-50:   map-get($clr-indigo-list, \"50\");\n$clr-indigo-100:  map-get($clr-indigo-list, \"100\");\n$clr-indigo-200:  map-get($clr-indigo-list, \"200\");\n$clr-indigo-300:  map-get($clr-indigo-list, \"300\");\n$clr-indigo-400:  map-get($clr-indigo-list, \"400\");\n$clr-indigo-500:  map-get($clr-indigo-list, \"500\");\n$clr-indigo-600:  map-get($clr-indigo-list, \"600\");\n$clr-indigo-700:  map-get($clr-indigo-list, \"700\");\n$clr-indigo-800:  map-get($clr-indigo-list, \"800\");\n$clr-indigo-900:  map-get($clr-indigo-list, \"900\");\n$clr-indigo-a100: map-get($clr-indigo-list, \"a100\");\n$clr-indigo-a200: map-get($clr-indigo-list, \"a200\");\n$clr-indigo-a400: map-get($clr-indigo-list, \"a400\");\n$clr-indigo-a700: map-get($clr-indigo-list, \"a700\");\n\n\n//\n// Blue\n//\n\n$clr-blue-list: (\n  \"base\": #2196f3,\n  \"50\":   #e3f2fd,\n  \"100\":  #bbdefb,\n  \"200\":  #90caf9,\n  \"300\":  #64b5f6,\n  \"400\":  #42a5f5,\n  \"500\":  #2196f3,\n  \"600\":  #1e88e5,\n  \"700\":  #1976d2,\n  \"800\":  #1565c0,\n  \"900\":  #0d47a1,\n  \"a100\": #82b1ff,\n  \"a200\": #448aff,\n  \"a400\": #2979ff,\n  \"a700\": #2962ff\n);\n\n$clr-blue:      map-get($clr-blue-list, \"base\");\n\n$clr-blue-50:   map-get($clr-blue-list, \"50\");\n$clr-blue-100:  map-get($clr-blue-list, \"100\");\n$clr-blue-200:  map-get($clr-blue-list, \"200\");\n$clr-blue-300:  map-get($clr-blue-list, \"300\");\n$clr-blue-400:  map-get($clr-blue-list, \"400\");\n$clr-blue-500:  map-get($clr-blue-list, \"500\");\n$clr-blue-600:  map-get($clr-blue-list, \"600\");\n$clr-blue-700:  map-get($clr-blue-list, \"700\");\n$clr-blue-800:  map-get($clr-blue-list, \"800\");\n$clr-blue-900:  map-get($clr-blue-list, \"900\");\n$clr-blue-a100: map-get($clr-blue-list, \"a100\");\n$clr-blue-a200: map-get($clr-blue-list, \"a200\");\n$clr-blue-a400: map-get($clr-blue-list, \"a400\");\n$clr-blue-a700: map-get($clr-blue-list, \"a700\");\n\n\n//\n// Light Blue\n//\n\n$clr-light-blue-list: (\n  \"base\": #03a9f4,\n  \"50\":   #e1f5fe,\n  \"100\":  #b3e5fc,\n  \"200\":  #81d4fa,\n  \"300\":  #4fc3f7,\n  \"400\":  #29b6f6,\n  \"500\":  #03a9f4,\n  \"600\":  #039be5,\n  \"700\":  #0288d1,\n  \"800\":  #0277bd,\n  \"900\":  #01579b,\n  \"a100\": #80d8ff,\n  \"a200\": #40c4ff,\n  \"a400\": #00b0ff,\n  \"a700\": #0091ea\n);\n\n$clr-light-blue:      map-get($clr-light-blue-list, \"base\");\n\n$clr-light-blue-50:   map-get($clr-light-blue-list, \"50\");\n$clr-light-blue-100:  map-get($clr-light-blue-list, \"100\");\n$clr-light-blue-200:  map-get($clr-light-blue-list, \"200\");\n$clr-light-blue-300:  map-get($clr-light-blue-list, \"300\");\n$clr-light-blue-400:  map-get($clr-light-blue-list, \"400\");\n$clr-light-blue-500:  map-get($clr-light-blue-list, \"500\");\n$clr-light-blue-600:  map-get($clr-light-blue-list, \"600\");\n$clr-light-blue-700:  map-get($clr-light-blue-list, \"700\");\n$clr-light-blue-800:  map-get($clr-light-blue-list, \"800\");\n$clr-light-blue-900:  map-get($clr-light-blue-list, \"900\");\n$clr-light-blue-a100: map-get($clr-light-blue-list, \"a100\");\n$clr-light-blue-a200: map-get($clr-light-blue-list, \"a200\");\n$clr-light-blue-a400: map-get($clr-light-blue-list, \"a400\");\n$clr-light-blue-a700: map-get($clr-light-blue-list, \"a700\");\n\n\n//\n// Cyan\n//\n\n$clr-cyan-list: (\n  \"base\": #00bcd4,\n  \"50\":   #e0f7fa,\n  \"100\":  #b2ebf2,\n  \"200\":  #80deea,\n  \"300\":  #4dd0e1,\n  \"400\":  #26c6da,\n  \"500\":  #00bcd4,\n  \"600\":  #00acc1,\n  \"700\":  #0097a7,\n  \"800\":  #00838f,\n  \"900\":  #006064,\n  \"a100\": #84ffff,\n  \"a200\": #18ffff,\n  \"a400\": #00e5ff,\n  \"a700\": #00b8d4\n);\n\n$clr-cyan:      map-get($clr-cyan-list, \"base\");\n\n$clr-cyan-50:   map-get($clr-cyan-list, \"50\");\n$clr-cyan-100:  map-get($clr-cyan-list, \"100\");\n$clr-cyan-200:  map-get($clr-cyan-list, \"200\");\n$clr-cyan-300:  map-get($clr-cyan-list, \"300\");\n$clr-cyan-400:  map-get($clr-cyan-list, \"400\");\n$clr-cyan-500:  map-get($clr-cyan-list, \"500\");\n$clr-cyan-600:  map-get($clr-cyan-list, \"600\");\n$clr-cyan-700:  map-get($clr-cyan-list, \"700\");\n$clr-cyan-800:  map-get($clr-cyan-list, \"800\");\n$clr-cyan-900:  map-get($clr-cyan-list, \"900\");\n$clr-cyan-a100: map-get($clr-cyan-list, \"a100\");\n$clr-cyan-a200: map-get($clr-cyan-list, \"a200\");\n$clr-cyan-a400: map-get($clr-cyan-list, \"a400\");\n$clr-cyan-a700: map-get($clr-cyan-list, \"a700\");\n\n\n//\n// Teal\n//\n\n$clr-teal-list: (\n  \"base\": #009688,\n  \"50\":   #e0f2f1,\n  \"100\":  #b2dfdb,\n  \"200\":  #80cbc4,\n  \"300\":  #4db6ac,\n  \"400\":  #26a69a,\n  \"500\":  #009688,\n  \"600\":  #00897b,\n  \"700\":  #00796b,\n  \"800\":  #00695c,\n  \"900\":  #004d40,\n  \"a100\": #a7ffeb,\n  \"a200\": #64ffda,\n  \"a400\": #1de9b6,\n  \"a700\": #00bfa5\n);\n\n$clr-teal:      map-get($clr-teal-list, \"base\");\n\n$clr-teal-50:   map-get($clr-teal-list, \"50\");\n$clr-teal-100:  map-get($clr-teal-list, \"100\");\n$clr-teal-200:  map-get($clr-teal-list, \"200\");\n$clr-teal-300:  map-get($clr-teal-list, \"300\");\n$clr-teal-400:  map-get($clr-teal-list, \"400\");\n$clr-teal-500:  map-get($clr-teal-list, \"500\");\n$clr-teal-600:  map-get($clr-teal-list, \"600\");\n$clr-teal-700:  map-get($clr-teal-list, \"700\");\n$clr-teal-800:  map-get($clr-teal-list, \"800\");\n$clr-teal-900:  map-get($clr-teal-list, \"900\");\n$clr-teal-a100: map-get($clr-teal-list, \"a100\");\n$clr-teal-a200: map-get($clr-teal-list, \"a200\");\n$clr-teal-a400: map-get($clr-teal-list, \"a400\");\n$clr-teal-a700: map-get($clr-teal-list, \"a700\");\n\n\n//\n// Green\n//\n\n$clr-green-list: (\n  \"base\": #4caf50,\n  \"50\":   #e8f5e9,\n  \"100\":  #c8e6c9,\n  \"200\":  #a5d6a7,\n  \"300\":  #81c784,\n  \"400\":  #66bb6a,\n  \"500\":  #4caf50,\n  \"600\":  #43a047,\n  \"700\":  #388e3c,\n  \"800\":  #2e7d32,\n  \"900\":  #1b5e20,\n  \"a100\": #b9f6ca,\n  \"a200\": #69f0ae,\n  \"a400\": #00e676,\n  \"a700\": #00c853\n);\n\n$clr-green:      map-get($clr-green-list, \"base\");\n\n$clr-green-50:   map-get($clr-green-list, \"50\");\n$clr-green-100:  map-get($clr-green-list, \"100\");\n$clr-green-200:  map-get($clr-green-list, \"200\");\n$clr-green-300:  map-get($clr-green-list, \"300\");\n$clr-green-400:  map-get($clr-green-list, \"400\");\n$clr-green-500:  map-get($clr-green-list, \"500\");\n$clr-green-600:  map-get($clr-green-list, \"600\");\n$clr-green-700:  map-get($clr-green-list, \"700\");\n$clr-green-800:  map-get($clr-green-list, \"800\");\n$clr-green-900:  map-get($clr-green-list, \"900\");\n$clr-green-a100: map-get($clr-green-list, \"a100\");\n$clr-green-a200: map-get($clr-green-list, \"a200\");\n$clr-green-a400: map-get($clr-green-list, \"a400\");\n$clr-green-a700: map-get($clr-green-list, \"a700\");\n\n\n//\n// Light green\n//\n\n$clr-light-green-list: (\n  \"base\": #8bc34a,\n  \"50\":   #f1f8e9,\n  \"100\":  #dcedc8,\n  \"200\":  #c5e1a5,\n  \"300\":  #aed581,\n  \"400\":  #9ccc65,\n  \"500\":  #8bc34a,\n  \"600\":  #7cb342,\n  \"700\":  #689f38,\n  \"800\":  #558b2f,\n  \"900\":  #33691e,\n  \"a100\": #ccff90,\n  \"a200\": #b2ff59,\n  \"a400\": #76ff03,\n  \"a700\": #64dd17\n);\n\n$clr-light-green:      map-get($clr-light-green-list, \"base\");\n\n$clr-light-green-50:   map-get($clr-light-green-list, \"50\");\n$clr-light-green-100:  map-get($clr-light-green-list, \"100\");\n$clr-light-green-200:  map-get($clr-light-green-list, \"200\");\n$clr-light-green-300:  map-get($clr-light-green-list, \"300\");\n$clr-light-green-400:  map-get($clr-light-green-list, \"400\");\n$clr-light-green-500:  map-get($clr-light-green-list, \"500\");\n$clr-light-green-600:  map-get($clr-light-green-list, \"600\");\n$clr-light-green-700:  map-get($clr-light-green-list, \"700\");\n$clr-light-green-800:  map-get($clr-light-green-list, \"800\");\n$clr-light-green-900:  map-get($clr-light-green-list, \"900\");\n$clr-light-green-a100: map-get($clr-light-green-list, \"a100\");\n$clr-light-green-a200: map-get($clr-light-green-list, \"a200\");\n$clr-light-green-a400: map-get($clr-light-green-list, \"a400\");\n$clr-light-green-a700: map-get($clr-light-green-list, \"a700\");\n\n\n//\n// Lime\n//\n\n$clr-lime-list: (\n  \"base\": #cddc39,\n  \"50\":   #f9fbe7,\n  \"100\":  #f0f4c3,\n  \"200\":  #e6ee9c,\n  \"300\":  #dce775,\n  \"400\":  #d4e157,\n  \"500\":  #cddc39,\n  \"600\":  #c0ca33,\n  \"700\":  #afb42b,\n  \"800\":  #9e9d24,\n  \"900\":  #827717,\n  \"a100\": #f4ff81,\n  \"a200\": #eeff41,\n  \"a400\": #c6ff00,\n  \"a700\": #aeea00\n);\n\n$clr-lime:      map-get($clr-lime-list, \"base\");\n\n$clr-lime-50:   map-get($clr-lime-list, \"50\");\n$clr-lime-100:  map-get($clr-lime-list, \"100\");\n$clr-lime-200:  map-get($clr-lime-list, \"200\");\n$clr-lime-300:  map-get($clr-lime-list, \"300\");\n$clr-lime-400:  map-get($clr-lime-list, \"400\");\n$clr-lime-500:  map-get($clr-lime-list, \"500\");\n$clr-lime-600:  map-get($clr-lime-list, \"600\");\n$clr-lime-700:  map-get($clr-lime-list, \"700\");\n$clr-lime-800:  map-get($clr-lime-list, \"800\");\n$clr-lime-900:  map-get($clr-lime-list, \"900\");\n$clr-lime-a100: map-get($clr-lime-list, \"a100\");\n$clr-lime-a200: map-get($clr-lime-list, \"a200\");\n$clr-lime-a400: map-get($clr-lime-list, \"a400\");\n$clr-lime-a700: map-get($clr-lime-list, \"a700\");\n\n\n//\n// Yellow\n//\n\n$clr-yellow-list: (\n  \"base\": #ffeb3b,\n  \"50\":   #fffde7,\n  \"100\":  #fff9c4,\n  \"200\":  #fff59d,\n  \"300\":  #fff176,\n  \"400\":  #ffee58,\n  \"500\":  #ffeb3b,\n  \"600\":  #fdd835,\n  \"700\":  #fbc02d,\n  \"800\":  #f9a825,\n  \"900\":  #f57f17,\n  \"a100\": #ffff8d,\n  \"a200\": #ffff00,\n  \"a400\": #ffea00,\n  \"a700\": #ffd600\n);\n\n$clr-yellow:      map-get($clr-yellow-list, \"base\");\n\n$clr-yellow-50:   map-get($clr-yellow-list, \"50\");\n$clr-yellow-100:  map-get($clr-yellow-list, \"100\");\n$clr-yellow-200:  map-get($clr-yellow-list, \"200\");\n$clr-yellow-300:  map-get($clr-yellow-list, \"300\");\n$clr-yellow-400:  map-get($clr-yellow-list, \"400\");\n$clr-yellow-500:  map-get($clr-yellow-list, \"500\");\n$clr-yellow-600:  map-get($clr-yellow-list, \"600\");\n$clr-yellow-700:  map-get($clr-yellow-list, \"700\");\n$clr-yellow-800:  map-get($clr-yellow-list, \"800\");\n$clr-yellow-900:  map-get($clr-yellow-list, \"900\");\n$clr-yellow-a100: map-get($clr-yellow-list, \"a100\");\n$clr-yellow-a200: map-get($clr-yellow-list, \"a200\");\n$clr-yellow-a400: map-get($clr-yellow-list, \"a400\");\n$clr-yellow-a700: map-get($clr-yellow-list, \"a700\");\n\n\n//\n// amber\n//\n\n$clr-amber-list: (\n  \"base\": #ffc107,\n  \"50\":   #fff8e1,\n  \"100\":  #ffecb3,\n  \"200\":  #ffe082,\n  \"300\":  #ffd54f,\n  \"400\":  #ffca28,\n  \"500\":  #ffc107,\n  \"600\":  #ffb300,\n  \"700\":  #ffa000,\n  \"800\":  #ff8f00,\n  \"900\":  #ff6f00,\n  \"a100\": #ffe57f,\n  \"a200\": #ffd740,\n  \"a400\": #ffc400,\n  \"a700\": #ffab00\n);\n\n$clr-amber:      map-get($clr-amber-list, \"base\");\n\n$clr-amber-50:   map-get($clr-amber-list, \"50\");\n$clr-amber-100:  map-get($clr-amber-list, \"100\");\n$clr-amber-200:  map-get($clr-amber-list, \"200\");\n$clr-amber-300:  map-get($clr-amber-list, \"300\");\n$clr-amber-400:  map-get($clr-amber-list, \"400\");\n$clr-amber-500:  map-get($clr-amber-list, \"500\");\n$clr-amber-600:  map-get($clr-amber-list, \"600\");\n$clr-amber-700:  map-get($clr-amber-list, \"700\");\n$clr-amber-800:  map-get($clr-amber-list, \"800\");\n$clr-amber-900:  map-get($clr-amber-list, \"900\");\n$clr-amber-a100: map-get($clr-amber-list, \"a100\");\n$clr-amber-a200: map-get($clr-amber-list, \"a200\");\n$clr-amber-a400: map-get($clr-amber-list, \"a400\");\n$clr-amber-a700: map-get($clr-amber-list, \"a700\");\n\n\n//\n// Orange\n//\n\n$clr-orange-list: (\n  \"base\": #ff9800,\n  \"50\":   #fff3e0,\n  \"100\":  #ffe0b2,\n  \"200\":  #ffcc80,\n  \"300\":  #ffb74d,\n  \"400\":  #ffa726,\n  \"500\":  #ff9800,\n  \"600\":  #fb8c00,\n  \"700\":  #f57c00,\n  \"800\":  #ef6c00,\n  \"900\":  #e65100,\n  \"a100\": #ffd180,\n  \"a200\": #ffab40,\n  \"a400\": #ff9100,\n  \"a700\": #ff6d00\n);\n\n$clr-orange:      map-get($clr-orange-list, \"base\");\n\n$clr-orange-50:   map-get($clr-orange-list, \"50\");\n$clr-orange-100:  map-get($clr-orange-list, \"100\");\n$clr-orange-200:  map-get($clr-orange-list, \"200\");\n$clr-orange-300:  map-get($clr-orange-list, \"300\");\n$clr-orange-400:  map-get($clr-orange-list, \"400\");\n$clr-orange-500:  map-get($clr-orange-list, \"500\");\n$clr-orange-600:  map-get($clr-orange-list, \"600\");\n$clr-orange-700:  map-get($clr-orange-list, \"700\");\n$clr-orange-800:  map-get($clr-orange-list, \"800\");\n$clr-orange-900:  map-get($clr-orange-list, \"900\");\n$clr-orange-a100: map-get($clr-orange-list, \"a100\");\n$clr-orange-a200: map-get($clr-orange-list, \"a200\");\n$clr-orange-a400: map-get($clr-orange-list, \"a400\");\n$clr-orange-a700: map-get($clr-orange-list, \"a700\");\n\n\n//\n// Deep orange\n//\n\n$clr-deep-orange-list: (\n  \"base\": #ff5722,\n  \"50\":   #fbe9e7,\n  \"100\":  #ffccbc,\n  \"200\":  #ffab91,\n  \"300\":  #ff8a65,\n  \"400\":  #ff7043,\n  \"500\":  #ff5722,\n  \"600\":  #f4511e,\n  \"700\":  #e64a19,\n  \"800\":  #d84315,\n  \"900\":  #bf360c,\n  \"a100\": #ff9e80,\n  \"a200\": #ff6e40,\n  \"a400\": #ff3d00,\n  \"a700\": #dd2c00\n);\n\n$clr-deep-orange:      map-get($clr-deep-orange-list, \"base\");\n\n$clr-deep-orange-50:   map-get($clr-deep-orange-list, \"50\");\n$clr-deep-orange-100:  map-get($clr-deep-orange-list, \"100\");\n$clr-deep-orange-200:  map-get($clr-deep-orange-list, \"200\");\n$clr-deep-orange-300:  map-get($clr-deep-orange-list, \"300\");\n$clr-deep-orange-400:  map-get($clr-deep-orange-list, \"400\");\n$clr-deep-orange-500:  map-get($clr-deep-orange-list, \"500\");\n$clr-deep-orange-600:  map-get($clr-deep-orange-list, \"600\");\n$clr-deep-orange-700:  map-get($clr-deep-orange-list, \"700\");\n$clr-deep-orange-800:  map-get($clr-deep-orange-list, \"800\");\n$clr-deep-orange-900:  map-get($clr-deep-orange-list, \"900\");\n$clr-deep-orange-a100: map-get($clr-deep-orange-list, \"a100\");\n$clr-deep-orange-a200: map-get($clr-deep-orange-list, \"a200\");\n$clr-deep-orange-a400: map-get($clr-deep-orange-list, \"a400\");\n$clr-deep-orange-a700: map-get($clr-deep-orange-list, \"a700\");\n\n\n//\n// Brown\n//\n\n$clr-brown-list: (\n  \"base\": #795548,\n  \"50\":   #efebe9,\n  \"100\":  #d7ccc8,\n  \"200\":  #bcaaa4,\n  \"300\":  #a1887f,\n  \"400\":  #8d6e63,\n  \"500\":  #795548,\n  \"600\":  #6d4c41,\n  \"700\":  #5d4037,\n  \"800\":  #4e342e,\n  \"900\":  #3e2723,\n);\n\n$clr-brown:     map-get($clr-brown-list, \"base\");\n\n$clr-brown-50:  map-get($clr-brown-list, \"50\");\n$clr-brown-100: map-get($clr-brown-list, \"100\");\n$clr-brown-200: map-get($clr-brown-list, \"200\");\n$clr-brown-300: map-get($clr-brown-list, \"300\");\n$clr-brown-400: map-get($clr-brown-list, \"400\");\n$clr-brown-500: map-get($clr-brown-list, \"500\");\n$clr-brown-600: map-get($clr-brown-list, \"600\");\n$clr-brown-700: map-get($clr-brown-list, \"700\");\n$clr-brown-800: map-get($clr-brown-list, \"800\");\n$clr-brown-900: map-get($clr-brown-list, \"900\");\n\n\n//\n// Grey\n//\n\n$clr-grey-list: (\n  \"base\": #9e9e9e,\n  \"50\":   #fafafa,\n  \"100\":  #f5f5f5,\n  \"200\":  #eeeeee,\n  \"300\":  #e0e0e0,\n  \"400\":  #bdbdbd,\n  \"500\":  #9e9e9e,\n  \"600\":  #757575,\n  \"700\":  #616161,\n  \"800\":  #424242,\n  \"900\":  #212121,\n);\n\n$clr-grey:     map-get($clr-grey-list, \"base\");\n\n$clr-grey-50:  map-get($clr-grey-list, \"50\");\n$clr-grey-100: map-get($clr-grey-list, \"100\");\n$clr-grey-200: map-get($clr-grey-list, \"200\");\n$clr-grey-300: map-get($clr-grey-list, \"300\");\n$clr-grey-400: map-get($clr-grey-list, \"400\");\n$clr-grey-500: map-get($clr-grey-list, \"500\");\n$clr-grey-600: map-get($clr-grey-list, \"600\");\n$clr-grey-700: map-get($clr-grey-list, \"700\");\n$clr-grey-800: map-get($clr-grey-list, \"800\");\n$clr-grey-900: map-get($clr-grey-list, \"900\");\n\n\n//\n// Blue grey\n//\n\n$clr-blue-grey-list: (\n  \"base\": #607d8b,\n  \"50\":   #eceff1,\n  \"100\":  #cfd8dc,\n  \"200\":  #b0bec5,\n  \"300\":  #90a4ae,\n  \"400\":  #78909c,\n  \"500\":  #607d8b,\n  \"600\":  #546e7a,\n  \"700\":  #455a64,\n  \"800\":  #37474f,\n  \"900\":  #263238,\n);\n\n$clr-blue-grey:     map-get($clr-blue-grey-list, \"base\");\n\n$clr-blue-grey-50:  map-get($clr-blue-grey-list, \"50\");\n$clr-blue-grey-100: map-get($clr-blue-grey-list, \"100\");\n$clr-blue-grey-200: map-get($clr-blue-grey-list, \"200\");\n$clr-blue-grey-300: map-get($clr-blue-grey-list, \"300\");\n$clr-blue-grey-400: map-get($clr-blue-grey-list, \"400\");\n$clr-blue-grey-500: map-get($clr-blue-grey-list, \"500\");\n$clr-blue-grey-600: map-get($clr-blue-grey-list, \"600\");\n$clr-blue-grey-700: map-get($clr-blue-grey-list, \"700\");\n$clr-blue-grey-800: map-get($clr-blue-grey-list, \"800\");\n$clr-blue-grey-900: map-get($clr-blue-grey-list, \"900\");\n\n\n//\n// Black\n//\n\n$clr-black-list: (\n  \"base\": #000\n);\n\n$clr-black: map-get($clr-black-list, \"base\");\n\n\n//\n// White\n//\n\n$clr-white-list: (\n  \"base\": #fff\n);\n\n$clr-white: map-get($clr-white-list, \"base\");\n\n\n//\n// List for all Colors for looping\n//\n\n$clr-list-all: (\n  \"red\":         $clr-red-list,\n  \"pink\":        $clr-pink-list,\n  \"purple\":      $clr-purple-list,\n  \"deep-purple\": $clr-deep-purple-list,\n  \"indigo\":      $clr-indigo-list,\n  \"blue\":        $clr-blue-list,\n  \"light-blue\":  $clr-light-blue-list,\n  \"cyan\":        $clr-cyan-list,\n  \"teal\":        $clr-teal-list,\n  \"green\":       $clr-green-list,\n  \"light-green\": $clr-light-green-list,\n  \"lime\":        $clr-lime-list,\n  \"yellow\":      $clr-yellow-list,\n  \"amber\":       $clr-amber-list,\n  \"orange\":      $clr-orange-list,\n  \"deep-orange\": $clr-deep-orange-list,\n  \"brown\":       $clr-brown-list,\n  \"grey\":        $clr-grey-list,\n  \"blue-grey\":   $clr-blue-grey-list,\n  \"black\":       $clr-black-list,\n  \"white\":       $clr-white-list\n);\n\n\n//\n// Typography\n//\n\n$clr-ui-display-4: $clr-grey-600;\n$clr-ui-display-3: $clr-grey-600;\n$clr-ui-display-2: $clr-grey-600;\n$clr-ui-display-1: $clr-grey-600;\n$clr-ui-headline:  $clr-grey-900;\n$clr-ui-title:     $clr-grey-900;\n$clr-ui-subhead-1: $clr-grey-900;\n$clr-ui-body-2:    $clr-grey-900;\n$clr-ui-body-1:    $clr-grey-900;\n$clr-ui-caption:   $clr-grey-600;\n$clr-ui-menu:      $clr-grey-900;\n$clr-ui-button:    $clr-grey-900;\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-footnotes-icon: svg-load(\"material/keyboard-return.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Footnote reference\n  [id^=\"fnref:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 4px);\n    padding-top: px2rem(48px + 24px - 4px);\n  }\n\n  // Footnote\n  [id^=\"fn:\"]:target {\n    scroll-margin-top: initial;\n    margin-top: -1 * px2rem(48px + 24px - 3px);\n    padding-top: px2rem(48px + 24px - 3px);\n  }\n\n  // Footnote container\n  .footnote {\n    color: var(--md-default-fg-color--light);\n    font-size: px2rem(12.8px);\n\n    // Footnote list - omit left indentation\n    ol {\n      margin-left: 0;\n    }\n\n    // Footnote list item\n    li {\n      transition: color 125ms;\n\n      // Darken color on target\n      &:target {\n        color: var(--md-default-fg-color);\n      }\n\n      // Show backreferences on footnote hover\n      &:hover  .footnote-backref,\n      &:target .footnote-backref {\n        transform: translateX(0);\n        opacity: 1;\n      }\n\n      // Adjust spacing on first child\n      > :first-child {\n        margin-top: 0;\n      }\n    }\n  }\n\n  // Footnote backreference\n  .footnote-backref {\n    display: inline-block;\n    color: var(--md-typeset-a-color);\n    // Hack: omit Unicode arrow for replacement with icon\n    font-size: 0;\n    vertical-align: text-bottom;\n    transform: translateX(px2rem(5px));\n    opacity: 0;\n    transition:\n      color     250ms,\n      transform 250ms 250ms,\n      opacity   125ms 250ms;\n\n    // [print]: Show footnote backreferences\n    @media print {\n      color: var(--md-typeset-a-color);\n      transform: translateX(0);\n      opacity: 1;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      transform: translateX(px2rem(-5px));\n    }\n\n    // Adjust color on hover\n    &:hover {\n      color: var(--md-accent-fg-color);\n    }\n\n    // Footnote backreference icon\n    &::before {\n      display: inline-block;\n      width: px2rem(16px);\n      height: px2rem(16px);\n      background-color: currentColor;\n      mask-image: var(--md-footnotes-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n\n        // Flip icon vertically\n        svg {\n          transform: scaleX(-1);\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Headerlink\n  .headerlink {\n    display: inline-block;\n    margin-left: px2rem(10px);\n    color: var(--md-default-fg-color--lighter);\n    opacity: 0;\n    transition:\n      color      250ms,\n      opacity    125ms;\n\n    // [print]: Hide headerlinks\n    @media print {\n      display: none;\n    }\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      margin-right: px2rem(10px);\n      margin-left: initial;\n    }\n  }\n\n  // Show headerlinks on parent hover\n  :hover  > .headerlink,\n  :target > .headerlink,\n  .headerlink:focus {\n    opacity: 1;\n    transition:\n      color      250ms,\n      opacity    125ms;\n  }\n\n  // Adjust color on parent target or focus/hover\n  :target > .headerlink,\n  .headerlink:focus,\n  .headerlink:hover {\n    color: var(--md-accent-fg-color);\n  }\n\n  // Adjust scroll offset for all elements with `id` attributes - general scroll\n  // margin offset for anything that can be targeted. Browser support is pretty\n  // decent by now, but Edge <79 and Safari (iOS and macOS) still don't support\n  // it properly, so we settle with a cross-browser anchor correction solution.\n  :target {\n    scroll-margin-top: px2rem(48px + 24px);\n  }\n\n  // Adjust scroll offset for headlines of level 1-3\n  h1:target,\n  h2:target,\n  h3:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 4px);\n      padding-top: px2rem(48px + 24px - 4px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 4\n  h4:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px - 3px);\n      padding-top: px2rem(48px + 24px - 3px);\n      content: \"\";\n    }\n  }\n\n  // Adjust scroll offset for headlines of level 5-6\n  h5:target,\n  h6:target {\n    scroll-margin-top: initial;\n\n    // Anchor correction hack\n    &::before {\n      display: block;\n      margin-top: -1 * px2rem(48px + 24px);\n      padding-top: px2rem(48px + 24px);\n      content: \"\";\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Arithmatex container\n  div.arithmatex {\n    overflow: auto;\n\n    // [mobile -]: Align with body copy\n    @include break-to-device(mobile) {\n      margin: 0 px2rem(-16px);\n    }\n\n    // Arithmatex content\n    > * {\n      width: min-content;\n      // stylelint-disable-next-line declaration-no-important\n      margin: 1em auto !important;\n      padding: 0 px2rem(16px);\n      touch-action: auto;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Deletion, addition or comment\n  del.critic,\n  ins.critic,\n  .critic.comment {\n    box-decoration-break: clone;\n  }\n\n  // Deletion\n  del.critic {\n    background-color: var(--md-typeset-del-color);\n  }\n\n  // Addition\n  ins.critic {\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Comment\n  .critic.comment {\n    color: var(--md-code-hl-comment-color);\n\n    // Comment opening mark\n    &::before {\n      content: \"/* \";\n    }\n\n    // Comment closing mark\n    &::after {\n      content: \" */\";\n    }\n  }\n\n  // Critic block\n  .critic.block {\n    display: block;\n    margin: 1em 0;\n    padding-right: px2rem(16px);\n    padding-left: px2rem(16px);\n    overflow: auto;\n    box-shadow: none;\n\n    // Adjust spacing on first child\n    > :first-child {\n      margin-top: 0.5em;\n    }\n\n    // Adjust spacing on last child\n    > :last-child {\n      margin-bottom: 0.5em;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-details-icon: svg-load(\"material/chevron-right.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Details\n  details {\n    @extend .admonition;\n\n    display: flow-root;\n    padding-top: 0;\n    overflow: visible;\n\n    // Details title icon - rotate icon on transition to open state\n    &[open] > summary::after {\n      transform: rotate(90deg);\n    }\n\n    // Adjust spacing for details in closed state\n    &:not([open]) {\n      padding-bottom: 0;\n      box-shadow: none;\n\n      // Hack: we cannot set `overflow: hidden` on the `details` element (which\n      // is why we set it to `overflow: visible`, as the outline would not be\n      // visible when focusing. Therefore, we must set the border radius on the\n      // summary explicitly.\n      > summary {\n        border-radius: px2rem(2px);\n      }\n    }\n\n    // Hack: omit margin collapse\n    &::after {\n      display: table;\n      content: \"\";\n    }\n  }\n\n  // Details title\n  summary {\n    @extend .admonition-title;\n\n    display: block;\n    min-height: px2rem(20px);\n    padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);\n    border-top-left-radius: px2rem(2px);\n    border-top-right-radius: px2rem(2px);\n    cursor: pointer;\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      padding: px2rem(8px) px2rem(44px) px2rem(8px) px2rem(36px);\n    }\n\n    // Hide outline for pointer devices\n    &:not(.focus-visible) {\n      outline: none;\n      -webkit-tap-highlight-color: transparent;\n    }\n\n    // Details marker\n    &::after {\n      position: absolute;\n      top: px2rem(8px);\n      right: px2rem(8px);\n      width: px2rem(20px);\n      height: px2rem(20px);\n      background-color: currentColor;\n      mask-image: var(--md-details-icon);\n      mask-repeat: no-repeat;\n      mask-size: contain;\n      transform: rotate(0deg);\n      transition: transform 250ms;\n      content: \"\";\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: initial;\n        left: px2rem(8px);\n        transform: rotate(180deg);\n      }\n    }\n\n    // Hide native details marker\n    &::marker,\n    &::-webkit-details-marker {\n      display: none;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Emoji and icon container\n  .emojione,\n  .twemoji,\n  .gemoji {\n    display: inline-flex;\n    height: px2em(18px);\n    vertical-align: text-top;\n\n    // Icon - inlined via mkdocs-material-extensions\n    svg {\n      width: px2em(18px);\n      max-height: 100%;\n      fill: currentColor;\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules: syntax highlighting\n// ----------------------------------------------------------------------------\n\n// Code block\n.highlight {\n  .o,   // Operator\n  .ow { // Operator, word\n    color: var(--md-code-hl-operator-color);\n  }\n\n  .p {  // Punctuation\n    color: var(--md-code-hl-punctuation-color);\n  }\n\n  .cpf, // Comment, preprocessor file\n  .l,   // Literal\n  .s,   // Literal, string\n  .sb,  // Literal, string backticks\n  .sc,  // Literal, string char\n  .s2,  // Literal, string double\n  .si,  // Literal, string interpol\n  .s1,  // Literal, string single\n  .ss { // Literal, string symbol\n    color: var(--md-code-hl-string-color);\n  }\n\n  .cp,  // Comment, pre-processor\n  .se,  // Literal, string escape\n  .sh,  // Literal, string heredoc\n  .sr,  // Literal, string regex\n  .sx { // Literal, string other\n    color: var(--md-code-hl-special-color);\n  }\n\n  .m,   // Number\n  .mb,  // Number, binary\n  .mf,  // Number, float\n  .mh,  // Number, hex\n  .mi,  // Number, integer\n  .il,  // Number, integer long\n  .mo { // Number, octal\n    color: var(--md-code-hl-number-color);\n  }\n\n  .k,   // Keyword,\n  .kd,  // Keyword, declaration\n  .kn,  // Keyword, namespace\n  .kp,  // Keyword, pseudo\n  .kr,  // Keyword, reserved\n  .kt { // Keyword, type\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .kc,  // Keyword, constant\n  .n {  // Name\n    color: var(--md-code-hl-name-color);\n  }\n\n  .no,  // Name, constant\n  .nb,  // Name, builtin\n  .bp { // Name, builtin pseudo\n    color: var(--md-code-hl-constant-color);\n  }\n\n  .nc,  // Name, class\n  .ne,  // Name, exception\n  .nf,  // Name, function\n  .nn { // Name, namespace\n    color: var(--md-code-hl-function-color);\n  }\n\n  .nd,  // Name, decorator\n  .ni,  // Name, entity\n  .nl,  // Name, label\n  .nt { // Name, tag\n    color: var(--md-code-hl-keyword-color);\n  }\n\n  .c,   // Comment\n  .cm,  // Comment, multiline\n  .c1,  // Comment, single\n  .ch,  // Comment, shebang\n  .cs,  // Comment, special\n  .sd { // Literal, string doc\n    color: var(--md-code-hl-comment-color);\n  }\n\n  .na,  // Name, attribute\n  .nv,  // Variable,\n  .vc,  // Variable, class\n  .vg,  // Variable, global\n  .vi { // Variable, instance\n    color: var(--md-code-hl-variable-color);\n  }\n\n  .ge,  // Generic, emph\n  .gr,  // Generic, error\n  .gh,  // Generic, heading\n  .go,  // Generic, output\n  .gp,  // Generic, prompt\n  .gs,  // Generic, strong\n  .gu,  // Generic, subheading\n  .gt { // Generic, traceback\n    color: var(--md-code-hl-generic-color);\n  }\n\n  .gd,  // Diff, delete\n  .gi { // Diff, insert\n    margin: 0 px2em(-2px);\n    padding: 0 px2em(2px);\n    border-radius: px2rem(2px);\n  }\n\n  .gd { // Diff, delete\n    background-color: var(--md-typeset-del-color);\n  }\n\n  .gi { // Diff, insert\n    background-color: var(--md-typeset-ins-color);\n  }\n\n  // Highlighted line\n  .hll {\n    display: block;\n    margin: 0 px2em(-16px, 13.6px);\n    padding: 0 px2em(16px, 13.6px);\n    background-color: var(--md-code-hl-color);\n  }\n\n  // Code block line numbers (inline)\n  [data-linenos]::before {\n    position: sticky;\n    left: px2em(-16px, 13.6px);\n    float: left;\n    margin-right: px2em(16px, 13.6px);\n    margin-left: px2em(-16px, 13.6px);\n    padding-left: px2em(16px, 13.6px);\n    color: var(--md-default-fg-color--light);\n    background-color: var(--md-code-bg-color);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n    content: attr(data-linenos);\n    user-select: none;\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: layout\n// ----------------------------------------------------------------------------\n\n// Code block with line numbers\n.highlighttable {\n  display: flow-root;\n  overflow: hidden;\n\n  // Set table elements to block layout, because otherwise the whole flexbox\n  // hacking won't work correctly\n  tbody,\n  td {\n    display: block;\n    padding: 0;\n  }\n\n  // We need to use flexbox layout, because otherwise it's not possible to\n  // make the code container scroll while keeping the line numbers static\n  tr {\n    display: flex;\n  }\n\n  // The pre tags are nested inside a table, so we need to omit the margin\n  // because it collapses below all the overflows\n  pre {\n    margin: 0;\n  }\n\n  // Code block line numbers - disable user selection, so code can be easily\n  // copied without accidentally also copying the line numbers\n  .linenos {\n    padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);\n    padding-right: 0;\n    font-size: px2em(13.6px);\n    background-color: var(--md-code-bg-color);\n    user-select: none;\n  }\n\n  // Code block line numbers container\n  .linenodiv {\n    padding-right: px2em(8px, 13.6px);\n    box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;\n\n    // Adjust colors and alignment\n    pre {\n      color: var(--md-default-fg-color--light);\n      text-align: right;\n    }\n  }\n\n  // Code block container - stretch to remaining space\n  .code {\n    flex: 1;\n    overflow: hidden;\n  }\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Code block with line numbers\n  .highlighttable {\n    margin: 1em 0;\n    direction: ltr;\n    border-radius: px2rem(2px);\n\n    // Omit rounded borders on contained code block\n    code {\n      border-radius: 0;\n    }\n  }\n\n  // [mobile -]: Align with body copy\n  @include break-to-device(mobile) {\n\n    // Top-level code block\n    > .highlight {\n      margin: 1em px2rem(-16px);\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n\n      // Omit rounded borders\n      code {\n        border-radius: 0;\n      }\n    }\n\n    // Top-level code block with line numbers\n    > .highlighttable {\n      margin: 1em px2rem(-16px);\n      border-radius: 0;\n\n      // Highlighted line\n      .hll {\n        margin: 0 px2rem(-16px);\n        padding: 0 px2rem(16px);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Keyboard key\n  .keys {\n\n    // Keyboard key icon\n    kbd::before,\n    kbd::after {\n      position: relative;\n      margin: 0;\n      color: inherit;\n      -moz-osx-font-smoothing: initial;\n      -webkit-font-smoothing: initial;\n    }\n\n    // Surrounding text\n    span {\n      padding: 0 px2em(3.2px);\n      color: var(--md-default-fg-color--light);\n    }\n\n    // Define keyboard keys with left icon\n    @each $name, $code in (\n\n      // Modifiers\n      \"alt\":           \"\\2387\",\n      \"left-alt\":      \"\\2387\",\n      \"right-alt\":     \"\\2387\",\n      \"command\":       \"\\2318\",\n      \"left-command\":  \"\\2318\",\n      \"right-command\": \"\\2318\",\n      \"control\":       \"\\2303\",\n      \"left-control\":  \"\\2303\",\n      \"right-control\": \"\\2303\",\n      \"meta\":          \"\\25C6\",\n      \"left-meta\":     \"\\25C6\",\n      \"right-meta\":    \"\\25C6\",\n      \"option\":        \"\\2325\",\n      \"left-option\":   \"\\2325\",\n      \"right-option\":  \"\\2325\",\n      \"shift\":         \"\\21E7\",\n      \"left-shift\":    \"\\21E7\",\n      \"right-shift\":   \"\\21E7\",\n      \"super\":         \"\\2756\",\n      \"left-super\":    \"\\2756\",\n      \"right-super\":   \"\\2756\",\n      \"windows\":       \"\\229E\",\n      \"left-windows\":  \"\\229E\",\n      \"right-windows\": \"\\229E\",\n\n      // Other keys\n      \"arrow-down\":    \"\\2193\",\n      \"arrow-left\":    \"\\2190\",\n      \"arrow-right\":   \"\\2192\",\n      \"arrow-up\":      \"\\2191\",\n      \"backspace\":     \"\\232B\",\n      \"backtab\":       \"\\21E4\",\n      \"caps-lock\":     \"\\21EA\",\n      \"clear\":         \"\\2327\",\n      \"context-menu\":  \"\\2630\",\n      \"delete\":        \"\\2326\",\n      \"eject\":         \"\\23CF\",\n      \"end\":           \"\\2913\",\n      \"escape\":        \"\\238B\",\n      \"home\":          \"\\2912\",\n      \"insert\":        \"\\2380\",\n      \"page-down\":     \"\\21DF\",\n      \"page-up\":       \"\\21DE\",\n      \"print-screen\":  \"\\2399\"\n    ) {\n      .key-#{$name} {\n        &::before {\n          padding-right: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n\n    // Define keyboard keys with right icon\n    @each $name, $code in (\n      \"tab\":           \"\\21E5\",\n      \"num-enter\":     \"\\2324\",\n      \"enter\":         \"\\23CE\"\n    ) {\n      .key-#{$name} {\n        &::after {\n          padding-left: px2em(6.4px);\n          content: $code;\n        }\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tabbed block content\n  .tabbed-content {\n    display: none;\n    order: 99;\n    width: 100%;\n    box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);\n\n    // [print]: Show all tabs (even hidden ones) when printing\n    @media print {\n      display: block;\n      order: initial;\n    }\n\n    // Code block is the only child of a tab - remove margin and mirror\n    // previous (now deprecated) SuperFences code block grouping behavior\n    > pre:only-child,\n    > .highlight:only-child pre,\n    > .highlighttable:only-child {\n      margin: 0;\n\n      // Omit rounded borders\n      > code {\n        border-top-left-radius: 0;\n        border-top-right-radius: 0;\n      }\n    }\n\n    // Adjust spacing for nested tab\n    > .tabbed-set {\n      margin: 0;\n    }\n  }\n\n  // Tabbed block container\n  .tabbed-set {\n    position: relative;\n    display: flex;\n    flex-wrap: wrap;\n    margin: 1em 0;\n    border-radius: px2rem(2px);\n\n    // Tab radio button - the Tabbed extension will generate radio buttons with\n    // labels, so tabs can be triggered without the necessity for JavaScript.\n    // This is pretty cool, as it has great accessibility out-of-the box, so\n    // we just hide the radio button and toggle the label color for indication.\n    > input {\n      position: absolute;\n      width: 0;\n      height: 0;\n      opacity: 0;\n\n      // Tab label for checked radio button\n      &:checked + label {\n        color: var(--md-accent-fg-color);\n        border-color: var(--md-accent-fg-color);\n\n        // Show tabbed block content\n        + .tabbed-content {\n          display: block;\n        }\n      }\n\n      // Tab label on focus\n      &:focus + label {\n        outline-style: auto;\n      }\n\n      // Hide outline for pointer devices\n      &:not(.focus-visible) + label {\n        outline: none;\n        -webkit-tap-highlight-color: transparent;\n      }\n    }\n\n    // Tab label\n    > label {\n      z-index: 1;\n      width: auto;\n      padding: px2em(12px, 12.8px) 1.25em px2em(10px, 12.8px);\n      color: var(--md-default-fg-color--light);\n      font-weight: 700;\n      font-size: px2rem(12.8px);\n      border-bottom: px2rem(2px) solid transparent;\n      cursor: pointer;\n      transition: color 250ms;\n\n      // Tab label on hover\n      &:hover {\n        color: var(--md-accent-fg-color);\n      }\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Icon definitions\n:root {\n  --md-tasklist-icon:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n  --md-tasklist-icon--checked:\n    svg-load(\"octicons/check-circle-fill-24.svg\");\n}\n\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // Tasklist item\n  .task-list-item {\n    position: relative;\n    list-style-type: none;\n\n    // Make checkbox items align with normal list items, but position\n    // everything in ems for correct layout at smaller font sizes\n    [type=\"checkbox\"] {\n      position: absolute;\n      top: 0.45em;\n      left: -2em;\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        right: -2em;\n        left: initial;\n      }\n    }\n  }\n\n  // Hide native checkbox, when custom classes are enabled\n  .task-list-control [type=\"checkbox\"] {\n    z-index: -1;\n    opacity: 0;\n  }\n\n  // Tasklist indicator in unchecked state\n  .task-list-indicator::before {\n    position: absolute;\n    top: 0.15em;\n    left: px2em(-24px);\n    width: px2em(20px);\n    height: px2em(20px);\n    background-color: var(--md-default-fg-color--lightest);\n    mask-image: var(--md-tasklist-icon);\n    mask-repeat: no-repeat;\n    mask-size: contain;\n    content: \"\";\n\n    // Adjust for right-to-left languages\n    [dir=\"rtl\"] & {\n      right: px2em(-24px);\n      left: initial;\n    }\n  }\n\n  // Tasklist indicator in checked state\n  [type=\"checkbox\"]:checked + .task-list-indicator::before {\n    background-color: $clr-green-a400;\n    mask-image: var(--md-tasklist-icon--checked);\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Scoped in typesetted content to match specificity of regular content\n.md-typeset {\n\n  // [tablet +]: Allow for rendering content as sidebars\n  @include break-from-device(tablet) {\n\n    // Modifier to float block elements\n    .inline {\n      float: left;\n      width: px2rem(234px);\n      margin-top: 0;\n      margin-right: px2rem(16px);\n      margin-bottom: px2rem(16px);\n\n      // Adjust for right-to-left languages\n      [dir=\"rtl\"] & {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n      }\n\n      // Modifier to move to end (ltr: right, rtl: left)\n      &.end {\n        float: right;\n        margin-right: 0;\n        margin-left: px2rem(16px);\n\n        // Adjust for right-to-left languages\n        [dir=\"rtl\"] & {\n          float: left;\n          margin-right: px2rem(16px);\n          margin-left: 0;\n        }\n      }\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/latest/assets/stylesheets/palette.7fa14f5b.min.css b/latest/assets/stylesheets/palette.7fa14f5b.min.css
deleted file mode 100644 (file)
index 1d46bb4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-[data-md-color-accent=red]{--md-accent-fg-color:#ff1a47;--md-accent-fg-color--transparent:rgba(255,26,71,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,136,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,0.7);--md-primary-fg-color--dark:rgba(0,0,0,0.07);--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:hover{background-color:rgba(0,0,0,.32)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,0.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__input:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}}@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,136,255,0.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-typeset-a-color:var(--md-primary-fg-color--light);--md-typeset-mark-color:rgba(66,136,255,0.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-admonition-bg-color:hsla(var(--md-hue),0%,100%,0.025);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1)}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5d6cc0}}
-/*# sourceMappingURL=palette.7fa14f5b.min.css.map */
\ No newline at end of file
diff --git a/latest/assets/stylesheets/palette.7fa14f5b.min.css.map b/latest/assets/stylesheets/palette.7fa14f5b.min.css.map
deleted file mode 100644 (file)
index 62a9e04..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/palette/_scheme.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/palette.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $color in (\n  \"red\":         $clr-red-a400,\n  \"pink\":        $clr-pink-a400,\n  \"purple\":      $clr-purple-a200,\n  \"deep-purple\": $clr-deep-purple-a200,\n  \"indigo\":      $clr-indigo-a200,\n  \"blue\":        $clr-blue-a200,\n  \"light-blue\":  $clr-light-blue-a700,\n  \"cyan\":        $clr-cyan-a700,\n  \"teal\":        $clr-teal-a700,\n  \"green\":       $clr-green-a700,\n  \"light-green\": $clr-light-green-a700,\n  \"lime\":        $clr-lime-a700,\n  \"yellow\":      $clr-yellow-a700,\n  \"amber\":       $clr-amber-a700,\n  \"orange\":      $clr-orange-a400,\n  \"deep-orange\": $clr-deep-orange-a200\n) {\n\n  // Color palette\n  [data-md-color-accent=\"#{$name}\"] {\n    --md-accent-fg-color:              hsla(#{hex2hsl($color)}, 1);\n    --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-accent-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-accent-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-accent-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-accent-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n","[data-md-color-accent=red] {\n  --md-accent-fg-color: hsla(348, 100%, 55%, 1);\n  --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=pink] {\n  --md-accent-fg-color: hsla(339, 100%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(339, 100%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=purple] {\n  --md-accent-fg-color: hsla(291, 96%, 62%, 1);\n  --md-accent-fg-color--transparent: hsla(291, 96%, 62%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=deep-purple] {\n  --md-accent-fg-color: hsla(256, 100%, 65%, 1);\n  --md-accent-fg-color--transparent: hsla(256, 100%, 65%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=indigo] {\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=blue] {\n  --md-accent-fg-color: hsla(218, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(218, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-blue] {\n  --md-accent-fg-color: hsla(203, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(203, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=cyan] {\n  --md-accent-fg-color: hsla(188, 100%, 42%, 1);\n  --md-accent-fg-color--transparent: hsla(188, 100%, 42%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=teal] {\n  --md-accent-fg-color: hsla(172, 100%, 37%, 1);\n  --md-accent-fg-color--transparent: hsla(172, 100%, 37%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=green] {\n  --md-accent-fg-color: hsla(145, 100%, 39%, 1);\n  --md-accent-fg-color--transparent: hsla(145, 100%, 39%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-green] {\n  --md-accent-fg-color: hsla(97, 81%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(97, 81%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=lime] {\n  --md-accent-fg-color: hsla(75, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(75, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=yellow] {\n  --md-accent-fg-color: hsla(50, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(50, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=amber] {\n  --md-accent-fg-color: hsla(40, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(40, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=orange] {\n  --md-accent-fg-color: hsla(34, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(34, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=deep-orange] {\n  --md-accent-fg-color: hsla(14, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(14, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=red] {\n  --md-primary-fg-color: hsla(1, 83%, 63%, 1);\n  --md-primary-fg-color--light: hsla(0, 69%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(1, 77%, 55%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=pink] {\n  --md-primary-fg-color: hsla(340, 82%, 52%, 1);\n  --md-primary-fg-color--light: hsla(340, 82%, 59%, 1);\n  --md-primary-fg-color--dark: hsla(336, 78%, 43%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=purple] {\n  --md-primary-fg-color: hsla(291, 47%, 51%, 1);\n  --md-primary-fg-color--light: hsla(291, 47%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(287, 65%, 40%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=deep-purple] {\n  --md-primary-fg-color: hsla(262, 47%, 55%, 1);\n  --md-primary-fg-color--light: hsla(262, 47%, 63%, 1);\n  --md-primary-fg-color--dark: hsla(262, 52%, 47%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=indigo] {\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue] {\n  --md-primary-fg-color: hsla(207, 90%, 54%, 1);\n  --md-primary-fg-color--light: hsla(207, 90%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(210, 79%, 46%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-blue] {\n  --md-primary-fg-color: hsla(199, 98%, 48%, 1);\n  --md-primary-fg-color--light: hsla(199, 92%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(201, 98%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=cyan] {\n  --md-primary-fg-color: hsla(187, 100%, 42%, 1);\n  --md-primary-fg-color--light: hsla(187, 71%, 50%, 1);\n  --md-primary-fg-color--dark: hsla(186, 100%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=teal] {\n  --md-primary-fg-color: hsla(174, 100%, 29%, 1);\n  --md-primary-fg-color--light: hsla(174, 63%, 40%, 1);\n  --md-primary-fg-color--dark: hsla(173, 100%, 24%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=green] {\n  --md-primary-fg-color: hsla(122, 39%, 49%, 1);\n  --md-primary-fg-color--light: hsla(123, 38%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(123, 43%, 39%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-green] {\n  --md-primary-fg-color: hsla(88, 50%, 53%, 1);\n  --md-primary-fg-color--light: hsla(88, 50%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(92, 48%, 42%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=lime] {\n  --md-primary-fg-color: hsla(66, 70%, 54%, 1);\n  --md-primary-fg-color--light: hsla(66, 70%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(62, 61%, 44%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=yellow] {\n  --md-primary-fg-color: hsla(54, 100%, 62%, 1);\n  --md-primary-fg-color--light: hsla(54, 100%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(43, 96%, 58%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=amber] {\n  --md-primary-fg-color: hsla(45, 100%, 51%, 1);\n  --md-primary-fg-color--light: hsla(45, 100%, 58%, 1);\n  --md-primary-fg-color--dark: hsla(38, 100%, 50%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=orange] {\n  --md-primary-fg-color: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--light: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(33, 100%, 49%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=deep-orange] {\n  --md-primary-fg-color: hsla(14, 100%, 63%, 1);\n  --md-primary-fg-color--light: hsla(14, 100%, 70%, 1);\n  --md-primary-fg-color--dark: hsla(14, 91%, 54%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=brown] {\n  --md-primary-fg-color: hsla(16, 25%, 38%, 1);\n  --md-primary-fg-color--light: hsla(16, 18%, 47%, 1);\n  --md-primary-fg-color--dark: hsla(14, 26%, 29%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=grey] {\n  --md-primary-fg-color: hsla(0, 0%, 46%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 62%, 1);\n  --md-primary-fg-color--dark: hsla(0, 0%, 38%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue-grey] {\n  --md-primary-fg-color: hsla(199, 18%, 40%, 1);\n  --md-primary-fg-color--light: hsla(200, 18%, 46%, 1);\n  --md-primary-fg-color--dark: hsla(199, 18%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=white] {\n  --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=white] .md-search__input {\n    background-color: rgba(0, 0, 0, 0.07);\n  }\n  [data-md-color-primary=white] .md-search__input + .md-search__icon {\n    color: rgba(0, 0, 0, 0.87);\n  }\n  [data-md-color-primary=white] .md-search__input::placeholder {\n    color: rgba(0, 0, 0, 0.54);\n  }\n  [data-md-color-primary=white] .md-search__input:hover {\n    background-color: rgba(0, 0, 0, 0.32);\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=white] .md-tabs {\n    border-bottom: 0.05rem solid rgba(0, 0, 0, 0.07);\n  }\n}\n\n[data-md-color-primary=black] {\n  --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n[data-md-color-primary=black] .md-header {\n  background-color: black;\n}\n@media screen and (max-width: 59.9375em) {\n  [data-md-color-primary=black] .md-nav__source {\n    background-color: rgba(0, 0, 0, 0.87);\n  }\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=black] .md-search__input {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-color-primary=black] .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.3);\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer] {\n    background-color: black;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=black] .md-tabs {\n    background-color: black;\n  }\n}\n\n@media screen {\n  [data-md-color-scheme=slate] {\n    --md-hue: 232;\n    --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n    --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n    --md-code-hl-color: hsla(218, 100%, 63%, 0.15);\n    --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n    --md-typeset-a-color: var(--md-primary-fg-color--light);\n    --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3);\n    --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n    --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n    --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n  }\n  [data-md-color-scheme=slate][data-md-color-primary=black], [data-md-color-scheme=slate][data-md-color-primary=white] {\n    --md-typeset-a-color: hsla(231, 44%, 56%, 1);\n  }\n}\n\n/*# sourceMappingURL=palette.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $colors in (\n  \"red\":         $clr-red-400         $clr-red-300         $clr-red-600,\n  \"pink\":        $clr-pink-500        $clr-pink-400        $clr-pink-700,\n  \"purple\":      $clr-purple-400      $clr-purple-300      $clr-purple-600,\n  \"deep-purple\": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,\n  \"indigo\":      $clr-indigo-500      $clr-indigo-400      $clr-indigo-700,\n  \"blue\":        $clr-blue-500        $clr-blue-400        $clr-blue-700,\n  \"light-blue\":  $clr-light-blue-500  $clr-light-blue-400  $clr-light-blue-700,\n  \"cyan\":        $clr-cyan-500        $clr-cyan-400        $clr-cyan-700,\n  \"teal\":        $clr-teal-500        $clr-teal-400        $clr-teal-700,\n  \"green\":       $clr-green-500       $clr-green-400       $clr-green-700,\n  \"light-green\": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,\n  \"lime\":        $clr-lime-500        $clr-lime-400        $clr-lime-700,\n  \"yellow\":      $clr-yellow-500      $clr-yellow-400      $clr-yellow-700,\n  \"amber\":       $clr-amber-500       $clr-amber-400       $clr-amber-700,\n  \"orange\":      $clr-orange-400      $clr-orange-400      $clr-orange-600,\n  \"deep-orange\": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,\n  \"brown\":       $clr-brown-500       $clr-brown-400       $clr-brown-700,\n  \"grey\":        $clr-grey-600        $clr-grey-500        $clr-grey-700,\n  \"blue-grey\":   $clr-blue-grey-600   $clr-blue-grey-500   $clr-blue-grey-700\n) {\n\n  // Color palette\n  [data-md-color-primary=\"#{$name}\"] {\n    --md-primary-fg-color:             hsla(#{hex2hsl(nth($colors, 1))}, 1);\n    --md-primary-fg-color--light:      hsla(#{hex2hsl(nth($colors, 2))}, 1);\n    --md-primary-fg-color--dark:       hsla(#{hex2hsl(nth($colors, 3))}, 1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-primary-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-primary-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-primary-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-primary-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: white\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"white\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light:        hsla(0, 0%, 0%, 0.54);\n\n  // Typeset color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // [tablet portrait +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 0%, 0.07);\n\n      // Search icon color\n      + .md-search__icon {\n        color: hsla(0, 0%, 0%, 0.87);\n      }\n\n      // Placeholder color\n      &::placeholder {\n        color: hsla(0, 0%, 0%, 0.54);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 0%, 0.32);\n      }\n    }\n  }\n\n  // [screen +]: Add bottom border for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: black\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"black\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Text color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // Header\n  .md-header {\n    background-color: hsla(0, 0%, 0%, 1);\n  }\n\n  // [tablet portrait -]: Layered navigation\n  @include break-to-device(tablet portrait) {\n\n    // Repository information container\n    .md-nav__source {\n      background-color: hsla(0, 0%, 0%, 0.87);\n    }\n  }\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 100%, 0.12);\n\n      // Search form on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.3);\n      }\n    }\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Site title in main navigation\n    html & .md-nav--primary .md-nav__title[for=\"__drawer\"] {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n\n  // [screen +]: Set background color for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Only use dark mode on screens\n@media screen {\n\n  // Slate theme, i.e. dark mode\n  [data-md-color-scheme=\"slate\"] {\n\n    // Slate's hue in the range [0,360] - change this variable to alter the tone\n    // of the theme, e.g. to make it more redish or greenish. This is a slate-\n    // specific variable, but the same approach may be adapted to custom themes.\n    --md-hue: 232;\n\n    // Default color shades\n    --md-default-fg-color:             hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light:      hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter:    hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest:   hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color:             hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light:      hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter:    hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest:   hsla(var(--md-hue), 15%, 21%, 0.07);\n\n    // Code color shades\n    --md-code-fg-color:                hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color:                hsla(var(--md-hue), 15%, 15%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-blue-a200)}, 0.15);\n    --md-code-hl-number-color:         hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color:       hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color:       hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color:         hsla(150, 58%, 44%, 1);\n\n    // Typeset color shades\n    --md-typeset-a-color:              var(--md-primary-fg-color--light);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-blue-a200)}, 0.3);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color:     hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color:     hsla(var(--md-hue), 15%, 14%, 1);\n\n    // Admonition color shades\n    --md-admonition-bg-color:          hsla(var(--md-hue), 0%, 100%, 0.025);\n\n    // Footer color shades\n    --md-footer-bg-color:              hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark:        hsla(var(--md-hue), 15%, 10%, 1);\n\n    // Black and white primary colors\n    &[data-md-color-primary=\"black\"],\n    &[data-md-color-primary=\"white\"] {\n\n      // Typeset color shades\n      --md-typeset-a-color:            hsla(#{hex2hsl($clr-indigo-400)}, 1);\n    }\n  }\n}\n"]}
\ No newline at end of file
diff --git a/latest/assets/stylesheets/palette.ef6f36e2.min.css b/latest/assets/stylesheets/palette.ef6f36e2.min.css
new file mode 100644 (file)
index 0000000..e0711bd
--- /dev/null
@@ -0,0 +1,2 @@
+[data-md-color-accent=red]{--md-accent-fg-color:#ff1a47;--md-accent-fg-color--transparent:rgba(255,26,71,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(83,108,254,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,136,255,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,0.1);--md-accent-bg-color:rgba(0,0,0,0.87);--md-accent-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,0.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7)}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,0.7);--md-primary-fg-color--dark:rgba(0,0,0,0.07);--md-primary-bg-color:rgba(0,0,0,0.87);--md-primary-bg-color--light:rgba(0,0,0,0.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:hover{background-color:rgba(0,0,0,.32)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,0.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__input:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}}@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,136,255,0.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(66,136,255,0.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-admonition-bg-color:hsla(var(--md-hue),0%,100%,0.025);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1)}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5d6cc0}}
+/*# sourceMappingURL=palette.ef6f36e2.min.css.map */
\ No newline at end of file
diff --git a/latest/assets/stylesheets/palette.ef6f36e2.min.css.map b/latest/assets/stylesheets/palette.ef6f36e2.min.css.map
new file mode 100644 (file)
index 0000000..da0a67b
--- /dev/null
@@ -0,0 +1 @@
+{"version":3,"sources":["src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/palette/_scheme.scss"],"names":[],"mappings":"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","file":"src/assets/stylesheets/palette.scss","sourcesContent":["////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $color in (\n  \"red\":         $clr-red-a400,\n  \"pink\":        $clr-pink-a400,\n  \"purple\":      $clr-purple-a200,\n  \"deep-purple\": $clr-deep-purple-a200,\n  \"indigo\":      $clr-indigo-a200,\n  \"blue\":        $clr-blue-a200,\n  \"light-blue\":  $clr-light-blue-a700,\n  \"cyan\":        $clr-cyan-a700,\n  \"teal\":        $clr-teal-a700,\n  \"green\":       $clr-green-a700,\n  \"light-green\": $clr-light-green-a700,\n  \"lime\":        $clr-lime-a700,\n  \"yellow\":      $clr-yellow-a700,\n  \"amber\":       $clr-amber-a700,\n  \"orange\":      $clr-orange-a400,\n  \"deep-orange\": $clr-deep-orange-a200\n) {\n\n  // Color palette\n  [data-md-color-accent=\"#{$name}\"] {\n    --md-accent-fg-color:              hsla(#{hex2hsl($color)}, 1);\n    --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-accent-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-accent-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-accent-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-accent-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n","[data-md-color-accent=red] {\n  --md-accent-fg-color: hsla(348, 100%, 55%, 1);\n  --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=pink] {\n  --md-accent-fg-color: hsla(339, 100%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(339, 100%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=purple] {\n  --md-accent-fg-color: hsla(291, 96%, 62%, 1);\n  --md-accent-fg-color--transparent: hsla(291, 96%, 62%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=deep-purple] {\n  --md-accent-fg-color: hsla(256, 100%, 65%, 1);\n  --md-accent-fg-color--transparent: hsla(256, 100%, 65%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=indigo] {\n  --md-accent-fg-color: hsla(231, 99%, 66%, 1);\n  --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=blue] {\n  --md-accent-fg-color: hsla(218, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(218, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-blue] {\n  --md-accent-fg-color: hsla(203, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(203, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=cyan] {\n  --md-accent-fg-color: hsla(188, 100%, 42%, 1);\n  --md-accent-fg-color--transparent: hsla(188, 100%, 42%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=teal] {\n  --md-accent-fg-color: hsla(172, 100%, 37%, 1);\n  --md-accent-fg-color--transparent: hsla(172, 100%, 37%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=green] {\n  --md-accent-fg-color: hsla(145, 100%, 39%, 1);\n  --md-accent-fg-color--transparent: hsla(145, 100%, 39%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=light-green] {\n  --md-accent-fg-color: hsla(97, 81%, 48%, 1);\n  --md-accent-fg-color--transparent: hsla(97, 81%, 48%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-accent=lime] {\n  --md-accent-fg-color: hsla(75, 100%, 46%, 1);\n  --md-accent-fg-color--transparent: hsla(75, 100%, 46%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=yellow] {\n  --md-accent-fg-color: hsla(50, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(50, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=amber] {\n  --md-accent-fg-color: hsla(40, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(40, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=orange] {\n  --md-accent-fg-color: hsla(34, 100%, 50%, 1);\n  --md-accent-fg-color--transparent: hsla(34, 100%, 50%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-accent=deep-orange] {\n  --md-accent-fg-color: hsla(14, 100%, 63%, 1);\n  --md-accent-fg-color--transparent: hsla(14, 100%, 63%, 0.1);\n  --md-accent-bg-color: hsla(0, 0%, 100%, 1);\n  --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=red] {\n  --md-primary-fg-color: hsla(1, 83%, 63%, 1);\n  --md-primary-fg-color--light: hsla(0, 69%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(1, 77%, 55%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=pink] {\n  --md-primary-fg-color: hsla(340, 82%, 52%, 1);\n  --md-primary-fg-color--light: hsla(340, 82%, 59%, 1);\n  --md-primary-fg-color--dark: hsla(336, 78%, 43%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=purple] {\n  --md-primary-fg-color: hsla(291, 47%, 51%, 1);\n  --md-primary-fg-color--light: hsla(291, 47%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(287, 65%, 40%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=deep-purple] {\n  --md-primary-fg-color: hsla(262, 47%, 55%, 1);\n  --md-primary-fg-color--light: hsla(262, 47%, 63%, 1);\n  --md-primary-fg-color--dark: hsla(262, 52%, 47%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=indigo] {\n  --md-primary-fg-color: hsla(231, 48%, 48%, 1);\n  --md-primary-fg-color--light: hsla(231, 44%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(232, 54%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue] {\n  --md-primary-fg-color: hsla(207, 90%, 54%, 1);\n  --md-primary-fg-color--light: hsla(207, 90%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(210, 79%, 46%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-blue] {\n  --md-primary-fg-color: hsla(199, 98%, 48%, 1);\n  --md-primary-fg-color--light: hsla(199, 92%, 56%, 1);\n  --md-primary-fg-color--dark: hsla(201, 98%, 41%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=cyan] {\n  --md-primary-fg-color: hsla(187, 100%, 42%, 1);\n  --md-primary-fg-color--light: hsla(187, 71%, 50%, 1);\n  --md-primary-fg-color--dark: hsla(186, 100%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=teal] {\n  --md-primary-fg-color: hsla(174, 100%, 29%, 1);\n  --md-primary-fg-color--light: hsla(174, 63%, 40%, 1);\n  --md-primary-fg-color--dark: hsla(173, 100%, 24%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=green] {\n  --md-primary-fg-color: hsla(122, 39%, 49%, 1);\n  --md-primary-fg-color--light: hsla(123, 38%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(123, 43%, 39%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=light-green] {\n  --md-primary-fg-color: hsla(88, 50%, 53%, 1);\n  --md-primary-fg-color--light: hsla(88, 50%, 60%, 1);\n  --md-primary-fg-color--dark: hsla(92, 48%, 42%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=lime] {\n  --md-primary-fg-color: hsla(66, 70%, 54%, 1);\n  --md-primary-fg-color--light: hsla(66, 70%, 61%, 1);\n  --md-primary-fg-color--dark: hsla(62, 61%, 44%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=yellow] {\n  --md-primary-fg-color: hsla(54, 100%, 62%, 1);\n  --md-primary-fg-color--light: hsla(54, 100%, 67%, 1);\n  --md-primary-fg-color--dark: hsla(43, 96%, 58%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=amber] {\n  --md-primary-fg-color: hsla(45, 100%, 51%, 1);\n  --md-primary-fg-color--light: hsla(45, 100%, 58%, 1);\n  --md-primary-fg-color--dark: hsla(38, 100%, 50%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=orange] {\n  --md-primary-fg-color: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--light: hsla(36, 100%, 57%, 1);\n  --md-primary-fg-color--dark: hsla(33, 100%, 49%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n}\n\n[data-md-color-primary=deep-orange] {\n  --md-primary-fg-color: hsla(14, 100%, 63%, 1);\n  --md-primary-fg-color--light: hsla(14, 100%, 70%, 1);\n  --md-primary-fg-color--dark: hsla(14, 91%, 54%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=brown] {\n  --md-primary-fg-color: hsla(16, 25%, 38%, 1);\n  --md-primary-fg-color--light: hsla(16, 18%, 47%, 1);\n  --md-primary-fg-color--dark: hsla(14, 26%, 29%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=grey] {\n  --md-primary-fg-color: hsla(0, 0%, 46%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 62%, 1);\n  --md-primary-fg-color--dark: hsla(0, 0%, 38%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=blue-grey] {\n  --md-primary-fg-color: hsla(199, 18%, 40%, 1);\n  --md-primary-fg-color--light: hsla(200, 18%, 46%, 1);\n  --md-primary-fg-color--dark: hsla(199, 18%, 33%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n}\n\n[data-md-color-primary=white] {\n  --md-primary-fg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=white] .md-search__input {\n    background-color: rgba(0, 0, 0, 0.07);\n  }\n  [data-md-color-primary=white] .md-search__input + .md-search__icon {\n    color: rgba(0, 0, 0, 0.87);\n  }\n  [data-md-color-primary=white] .md-search__input::placeholder {\n    color: rgba(0, 0, 0, 0.54);\n  }\n  [data-md-color-primary=white] .md-search__input:hover {\n    background-color: rgba(0, 0, 0, 0.32);\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=white] .md-tabs {\n    border-bottom: 0.05rem solid rgba(0, 0, 0, 0.07);\n  }\n}\n\n[data-md-color-primary=black] {\n  --md-primary-fg-color: hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color: hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);\n  --md-typeset-a-color: hsla(231, 48%, 48%, 1);\n}\n[data-md-color-primary=black] .md-header {\n  background-color: black;\n}\n@media screen and (max-width: 59.9375em) {\n  [data-md-color-primary=black] .md-nav__source {\n    background-color: rgba(0, 0, 0, 0.87);\n  }\n}\n@media screen and (min-width: 60em) {\n  [data-md-color-primary=black] .md-search__input {\n    background-color: rgba(255, 255, 255, 0.12);\n  }\n  [data-md-color-primary=black] .md-search__input:hover {\n    background-color: rgba(255, 255, 255, 0.3);\n  }\n}\n@media screen and (max-width: 76.1875em) {\n  html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer] {\n    background-color: black;\n  }\n}\n@media screen and (min-width: 76.25em) {\n  [data-md-color-primary=black] .md-tabs {\n    background-color: black;\n  }\n}\n\n@media screen {\n  [data-md-color-scheme=slate] {\n    --md-hue: 232;\n    --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);\n    --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);\n    --md-code-hl-color: hsla(218, 100%, 63%, 0.15);\n    --md-code-hl-number-color: hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color: hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color: hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color: hsla(150, 58%, 44%, 1);\n    --md-code-hl-name-color: var(--md-code-fg-color);\n    --md-code-hl-operator-color: var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color: var(--md-default-fg-color--light);\n    --md-code-hl-comment-color: var(--md-default-fg-color--light);\n    --md-code-hl-generic-color: var(--md-default-fg-color--light);\n    --md-code-hl-variable-color: var(--md-default-fg-color--light);\n    --md-typeset-color: var(--md-default-fg-color);\n    --md-typeset-a-color: var(--md-primary-fg-color);\n    --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3);\n    --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);\n    --md-admonition-bg-color: hsla(var(--md-hue), 0%, 100%, 0.025);\n    --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);\n  }\n  [data-md-color-scheme=slate][data-md-color-primary=black], [data-md-color-scheme=slate][data-md-color-primary=white] {\n    --md-typeset-a-color: hsla(231, 44%, 56%, 1);\n  }\n}\n\n/*# sourceMappingURL=palette.css.map */","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n@each $name, $colors in (\n  \"red\":         $clr-red-400         $clr-red-300         $clr-red-600,\n  \"pink\":        $clr-pink-500        $clr-pink-400        $clr-pink-700,\n  \"purple\":      $clr-purple-400      $clr-purple-300      $clr-purple-600,\n  \"deep-purple\": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,\n  \"indigo\":      $clr-indigo-500      $clr-indigo-400      $clr-indigo-700,\n  \"blue\":        $clr-blue-500        $clr-blue-400        $clr-blue-700,\n  \"light-blue\":  $clr-light-blue-500  $clr-light-blue-400  $clr-light-blue-700,\n  \"cyan\":        $clr-cyan-500        $clr-cyan-400        $clr-cyan-700,\n  \"teal\":        $clr-teal-500        $clr-teal-400        $clr-teal-700,\n  \"green\":       $clr-green-500       $clr-green-400       $clr-green-700,\n  \"light-green\": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,\n  \"lime\":        $clr-lime-500        $clr-lime-400        $clr-lime-700,\n  \"yellow\":      $clr-yellow-500      $clr-yellow-400      $clr-yellow-700,\n  \"amber\":       $clr-amber-500       $clr-amber-400       $clr-amber-700,\n  \"orange\":      $clr-orange-400      $clr-orange-400      $clr-orange-600,\n  \"deep-orange\": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,\n  \"brown\":       $clr-brown-500       $clr-brown-400       $clr-brown-700,\n  \"grey\":        $clr-grey-600        $clr-grey-500        $clr-grey-700,\n  \"blue-grey\":   $clr-blue-grey-600   $clr-blue-grey-500   $clr-blue-grey-700\n) {\n\n  // Color palette\n  [data-md-color-primary=\"#{$name}\"] {\n    --md-primary-fg-color:             hsla(#{hex2hsl(nth($colors, 1))}, 1);\n    --md-primary-fg-color--light:      hsla(#{hex2hsl(nth($colors, 2))}, 1);\n    --md-primary-fg-color--dark:       hsla(#{hex2hsl(nth($colors, 3))}, 1);\n\n    // Inverted text for lighter shades\n    @if index(\"lime\" \"yellow\" \"amber\" \"orange\", $name) {\n      --md-primary-bg-color:           hsla(0, 0%, 0%, 0.87);\n      --md-primary-bg-color--light:    hsla(0, 0%, 0%, 0.54);\n    } @else {\n      --md-primary-bg-color:           hsla(0, 0%, 100%, 1);\n      --md-primary-bg-color--light:    hsla(0, 0%, 100%, 0.7);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: white\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"white\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 100%, 0.7);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 0.07);\n  --md-primary-bg-color:               hsla(0, 0%, 0%, 0.87);\n  --md-primary-bg-color--light:        hsla(0, 0%, 0%, 0.54);\n\n  // Typeset color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // [tablet portrait +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 0%, 0.07);\n\n      // Search icon color\n      + .md-search__icon {\n        color: hsla(0, 0%, 0%, 0.87);\n      }\n\n      // Placeholder color\n      &::placeholder {\n        color: hsla(0, 0%, 0%, 0.54);\n      }\n\n      // Search input on hover\n      &:hover {\n        background-color: hsla(0, 0%, 0%, 0.32);\n      }\n    }\n  }\n\n  // [screen +]: Add bottom border for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);\n    }\n  }\n}\n\n// ----------------------------------------------------------------------------\n// Rules: black\n// ----------------------------------------------------------------------------\n\n// Color palette\n[data-md-color-primary=\"black\"] {\n  --md-primary-fg-color:               hsla(0, 0%, 0%, 1);\n  --md-primary-fg-color--light:        hsla(0, 0%, 0%, 0.54);\n  --md-primary-fg-color--dark:         hsla(0, 0%, 0%, 1);\n  --md-primary-bg-color:               hsla(0, 0%, 100%, 1);\n  --md-primary-bg-color--light:        hsla(0, 0%, 100%, 0.7);\n\n  // Text color shades\n  --md-typeset-a-color:                hsla(#{hex2hsl($clr-indigo-500)}, 1);\n\n  // Header\n  .md-header {\n    background-color: hsla(0, 0%, 0%, 1);\n  }\n\n  // [tablet portrait -]: Layered navigation\n  @include break-to-device(tablet portrait) {\n\n    // Repository information container\n    .md-nav__source {\n      background-color: hsla(0, 0%, 0%, 0.87);\n    }\n  }\n\n  // [tablet landscape +]: Header-embedded search\n  @include break-from-device(tablet landscape) {\n\n    // Search input\n    .md-search__input {\n      background-color: hsla(0, 0%, 100%, 0.12);\n\n      // Search form on hover\n      &:hover {\n        background-color: hsla(0, 0%, 100%, 0.3);\n      }\n    }\n  }\n\n  // [tablet -]: Layered navigation\n  @include break-to-device(tablet) {\n\n    // Site title in main navigation\n    html & .md-nav--primary .md-nav__title[for=\"__drawer\"] {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n\n  // [screen +]: Set background color for tabs\n  @include break-from-device(screen) {\n\n    // Navigation tabs\n    .md-tabs {\n      background-color: hsla(0, 0%, 0%, 1);\n    }\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Variables\n// ----------------------------------------------------------------------------\n\n///\n/// Device-specific breakpoints\n///\n/// @example\n///   $break-devices: (\n///     mobile: (\n///       portrait:  220px  479px,\n///       landscape: 480px  719px\n///     ),\n///     tablet: (\n///       portrait:  720px  959px,\n///       landscape: 960px  1219px\n///     ),\n///     screen: (\n///       small:     1220px 1599px,\n///       medium:    1600px 1999px,\n///       large:     2000px\n///     )\n///   );\n///\n$break-devices: () !default;\n\n// ----------------------------------------------------------------------------\n// Helpers\n// ----------------------------------------------------------------------------\n\n///\n/// Choose minimum and maximum device widths\n///\n@function break-select-min-max($devices) {\n  $min: 1000000;\n  $max: 0;\n  @each $key, $value in $devices {\n    @while type-of($value) == map {\n      $value: break-select-min-max($value);\n    }\n    @if type-of($value) == list {\n      @each $number in $value {\n        @if type-of($number) == number {\n          $min: min($number, $min);\n          @if $max {\n            $max: max($number, $max);\n          }\n        } @else {\n          @error \"Invalid number: #{$number}\";\n        }\n      }\n    } @else if type-of($value) == number {\n      $min: min($value, $min);\n      $max: null;\n    } @else {\n      @error \"Invalid value: #{$value}\";\n    }\n  }\n  @return $min, $max;\n}\n\n///\n/// Select minimum and maximum widths for a device breakpoint\n///\n@function break-select-device($device) {\n  $current: $break-devices;\n  @for $n from 1 through length($device) {\n    @if type-of($current) == map {\n      $current: map-get($current, nth($device, $n));\n    } @else {\n      @error \"Invalid device map: #{$devices}\";\n    }\n  }\n  @if type-of($current) == list or type-of($current) == number {\n    $current: (default: $current);\n  }\n  @return break-select-min-max($current);\n}\n\n// ----------------------------------------------------------------------------\n// Mixins\n// ----------------------------------------------------------------------------\n\n///\n/// A minimum-maximum media query breakpoint\n///\n@mixin break-at($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (min-width: $breakpoint) {\n      @content;\n    }\n  } @else if type-of($breakpoint) == list {\n    $min: nth($breakpoint, 1);\n    $max: nth($breakpoint, 2);\n    @if type-of($min) == number and type-of($max) == number {\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid breakpoint: #{$breakpoint}\";\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// An orientation media query breakpoint\n///\n@mixin break-at-orientation($breakpoint) {\n  @if type-of($breakpoint) == string {\n    @media screen and (orientation: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A maximum-aspect-ratio media query breakpoint\n///\n@mixin break-at-ratio($breakpoint) {\n  @if type-of($breakpoint) == number {\n    @media screen and (max-aspect-ratio: $breakpoint) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid breakpoint: #{$breakpoint}\";\n  }\n}\n\n///\n/// A minimum-maximum media query device breakpoint\n///\n@mixin break-at-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    @if nth($breakpoint, 2) {\n      $min: nth($breakpoint, 1);\n      $max: nth($breakpoint, 2);\n\n      @media screen and (min-width: $min) and (max-width: $max) {\n        @content;\n      }\n    } @else {\n      @error \"Invalid device: #{$device}\";\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A minimum media query device breakpoint\n///\n@mixin break-from-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $min: nth($breakpoint, 1);\n\n    @media screen and (min-width: $min) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n\n///\n/// A maximum media query device breakpoint\n///\n@mixin break-to-device($device) {\n  @if type-of($device) == string {\n    $device: $device,;\n  }\n  @if type-of($device) == list {\n    $breakpoint: break-select-device($device);\n    $max: nth($breakpoint, 2);\n\n    @media screen and (max-width: $max) {\n      @content;\n    }\n  } @else {\n    @error \"Invalid device: #{$device}\";\n  }\n}\n","////\n/// Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>\n///\n/// Permission is hereby granted, free of charge, to any person obtaining a\n/// copy of this software and associated documentation files (the \"Software\"),\n/// to deal in the Software without restriction, including without limitation\n/// the rights to use, copy, modify, merge, publish, distribute, sublicense,\n/// and/or sell copies of the Software, and to permit persons to whom the\n/// Software is furnished to do so, subject to the following conditions:\n///\n/// The above copyright notice and this permission notice shall be included in\n/// all copies or substantial portions of the Software.\n///\n/// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n/// DEALINGS\n////\n\n// ----------------------------------------------------------------------------\n// Rules\n// ----------------------------------------------------------------------------\n\n// Only use dark mode on screens\n@media screen {\n\n  // Slate theme, i.e. dark mode\n  [data-md-color-scheme=\"slate\"] {\n\n    // Slate's hue in the range [0,360] - change this variable to alter the tone\n    // of the theme, e.g. to make it more redish or greenish. This is a slate-\n    // specific variable, but the same approach may be adapted to custom themes.\n    --md-hue: 232;\n\n    // Default color shades\n    --md-default-fg-color:             hsla(var(--md-hue), 75%, 95%, 1);\n    --md-default-fg-color--light:      hsla(var(--md-hue), 75%, 90%, 0.62);\n    --md-default-fg-color--lighter:    hsla(var(--md-hue), 75%, 90%, 0.32);\n    --md-default-fg-color--lightest:   hsla(var(--md-hue), 75%, 90%, 0.12);\n    --md-default-bg-color:             hsla(var(--md-hue), 15%, 21%, 1);\n    --md-default-bg-color--light:      hsla(var(--md-hue), 15%, 21%, 0.54);\n    --md-default-bg-color--lighter:    hsla(var(--md-hue), 15%, 21%, 0.26);\n    --md-default-bg-color--lightest:   hsla(var(--md-hue), 15%, 21%, 0.07);\n\n    // Code color shades\n    --md-code-fg-color:                hsla(var(--md-hue), 18%, 86%, 1);\n    --md-code-bg-color:                hsla(var(--md-hue), 15%, 15%, 1);\n\n    // Code highlighting color shades\n    --md-code-hl-color:                hsla(#{hex2hsl($clr-blue-a200)}, 0.15);\n    --md-code-hl-number-color:         hsla(6, 74%, 63%, 1);\n    --md-code-hl-special-color:        hsla(340, 83%, 66%, 1);\n    --md-code-hl-function-color:       hsla(291, 57%, 65%, 1);\n    --md-code-hl-constant-color:       hsla(250, 62%, 70%, 1);\n    --md-code-hl-keyword-color:        hsla(219, 66%, 64%, 1);\n    --md-code-hl-string-color:         hsla(150, 58%, 44%, 1);\n    --md-code-hl-name-color:           var(--md-code-fg-color);\n    --md-code-hl-operator-color:       var(--md-default-fg-color--light);\n    --md-code-hl-punctuation-color:    var(--md-default-fg-color--light);\n    --md-code-hl-comment-color:        var(--md-default-fg-color--light);\n    --md-code-hl-generic-color:        var(--md-default-fg-color--light);\n    --md-code-hl-variable-color:       var(--md-default-fg-color--light);\n\n    // Typeset color shades\n    --md-typeset-color:                var(--md-default-fg-color);\n    --md-typeset-a-color:              var(--md-primary-fg-color);\n\n    // Typeset `mark` color shades\n    --md-typeset-mark-color:           hsla(#{hex2hsl($clr-blue-a200)}, 0.3);\n\n    // Typeset `kbd` color shades\n    --md-typeset-kbd-color:            hsla(var(--md-hue), 15%, 94%, 0.12);\n    --md-typeset-kbd-accent-color:     hsla(var(--md-hue), 15%, 94%, 0.2);\n    --md-typeset-kbd-border-color:     hsla(var(--md-hue), 15%, 14%, 1);\n\n    // Admonition color shades\n    --md-admonition-bg-color:          hsla(var(--md-hue), 0%, 100%, 0.025);\n\n    // Footer color shades\n    --md-footer-bg-color:              hsla(var(--md-hue), 15%, 12%, 0.87);\n    --md-footer-bg-color--dark:        hsla(var(--md-hue), 15%, 10%, 1);\n\n    // Black and white primary colors\n    &[data-md-color-primary=\"black\"],\n    &[data-md-color-primary=\"white\"] {\n\n      // Typeset color shades\n      --md-typeset-a-color:            hsla(#{hex2hsl($clr-indigo-400)}, 1);\n    }\n  }\n}\n"]}
\ No newline at end of file
index 3e6a449253f1ae728480e5788f835577f060bcd5..14ce9737ffaa9073273a26595c0ca88db538fa31 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href=".." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href=".." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href=".." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href=".." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2235,6 +2231,7 @@ page would render. The included templates <code>header</code> and <code>footer</
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2285,10 +2282,10 @@ page would render. The included templates <code>header</code> and <code>footer</
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e4bf2048ac62092a7e0b8177630c06b021703b38..12d6459f27ee162fc50920b2b3f1a5128b69f5fe 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL(".",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": ".", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": ".", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3ff72a68add6d6fb4b9bb11d68f9d0c9b897d281..faffb3b5ccc8f1e9ec570574759c9fff2ecc6ec1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1890,6 +1886,7 @@ class <code>jsImageViewer</code> that points to the full version.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1954,10 +1951,10 @@ class <code>jsImageViewer</code> that points to the full version.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b583f95a6de686b0fce7269e65d26328591ddacf..3a3c35caa3eff2e0bd440c78ec1b48a162cc0b6f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2040,6 +2036,7 @@ entirely if you do not provide a tiny build for your file.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2104,10 +2101,10 @@ entirely if you do not provide a tiny build for your file.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dacfea2a041538ba1ac90c9d0083a10b9eaf8d82..12a01d297f5cad5aaf46d112e4af3f9f2b872941 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2491,6 +2487,7 @@ to learn more about this function.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2555,10 +2552,10 @@ to learn more about this function.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index d81da40efa02e1e4ea466bc277e9a63c3c40bc96..fa63650f0edc5c00c8d7e37d0768b218a433509d 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2201,6 +2197,7 @@ construct in ES5, they are represented by mere objects that act as an instance.<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2265,10 +2262,10 @@ construct in ES5, they are represented by mere objects that act as an instance.<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 768e45a85f6cd89eea91847e89eb88d355fa9d8a..0d7f2b401730feb8f0c50a566c6216862771d4a7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2334,6 +2330,7 @@ modules from altering the behavior. It is strongly  recommended to always use
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2398,10 +2395,10 @@ modules from altering the behavior. It is strongly  recommended to always use
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 2d37c3a9cf7fede2bba2477f156c854156958796..155c4ba61a004ba4e585197336882fb7f8bd5397 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2129,6 +2125,7 @@ recommended to use feature detection instead.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2193,10 +2190,10 @@ recommended to use feature detection instead.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 80878354f15fa18d3e6bd7c348df163da8b12ff9..3d79ac05a65c424ab55414550b25d19bca6aee0c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2285,6 +2281,7 @@ function of <code>escapeHTML()</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2349,10 +2346,10 @@ function of <code>escapeHTML()</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6a0f83191035d56cd4a77cdf968ccbd59919f726..8661b9d92bd3444288afd341e221b10af9ef1e2f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2247,6 +2243,7 @@ the key is not part of the collection.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2311,10 +2308,10 @@ the key is not part of the collection.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index af0ae728ff4bf5db6ce7da197e64a2a9e4c63726..aa62b65eda8bd3d40ca4e8df7999be9c97119ab0 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2282,6 +2278,7 @@ key is <code>.content</code> which holds a reference to the dialog's inner conte
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2346,10 +2343,10 @@ key is <code>.content</code> which holds a reference to the dialog's inner conte
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 79dbd6540bd63cf6e973a78818b08540b0d024d8..d3a0f634e90d645d6119ea274d515cf9cb797ba7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2091,6 +2087,7 @@ listener that does not rely on a <code>MutationObserver</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2155,10 +2152,10 @@ listener that does not rely on a <code>MutationObserver</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0134047553d613cd0c4e4b53c34b9a1068ae80ca..011173ca5d6ed4395f3476627e5dfa36d6a0894a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2170,6 +2166,7 @@ of <code>suffix</code>.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2234,10 +2231,10 @@ of <code>suffix</code>.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e047315ac1867270f249cc06be79e81596a0f658..37bae8ee50ae6a071a3b095dd3fc78a3b0758cb4 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2374,6 +2370,7 @@ message for Ajax based actions.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2438,10 +2435,10 @@ message for Ajax based actions.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 30c21b043fc6d6f8aa766913aefe80b51df23b0e..d4418ead56bceead8958658e9ef5f5af92eb8935 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2151,6 +2147,7 @@ it is strongly recommended to use the aliases for consistency.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2215,10 +2212,10 @@ it is strongly recommended to use the aliases for consistency.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 717320bd62bc6c3195763fb31897c7aa6befe877..2e8b67a1b09eeb7ad0e452feea0f1f605f8daa68 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6db733ae6afc84d5a65bf6a5d4e6033b0a02637d..9cfbd9407b7ca5c9ac1c30a4842fdff83cdead3d 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 729c86391a721cb9660b9d1dc91a451c357fca99..32ee1a2f47601a58a8fe03300ac5acb17bbc65d8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a742dae1ba577ca041602ef23f18e9378903cbcb..ace919df2a9c5014040a6a2f6630df209309ea38 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f19295c3b6e833cd8d9da691a196a6371ebdf9e1..061d40b7b59c3d795756984657546ddcc3216cfa 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 590528ba1dbc8cefd9e8ae866326c14b9cbe6019..153a137d32d17aa211167fcf6931cb08d2f817c9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index aab83f796654fc2f5909e1675c55509fb177d02d..7ed9e7178d067d1dd828a4c214eaa78d04d8e9b4 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dbd68da13d3b371d8ffc234c0001a09cd735a45f..0cc62f95d4c865acc55d201dbfc870d1839a6eb6 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b03f6f8aa30e27a16caae13914036c31749af723..9f83dd5bedcacc90641dec5402b86d210df12917 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index eef944a143b2b39c951297d3075907d9fb2c516d..c63e042e9bafeaa455b0b0efc6c22ed22f0d789c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2242,6 +2238,7 @@ All changes are due to extending <code>AbstractFormBuilderForm</code>:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2273,10 +2270,10 @@ All changes are due to extending <code>AbstractFormBuilderForm</code>:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6144c1edc477e94075fabfbe85a3abb6fbe7ddc9..09de994d55ccee5fbd57f195842e11c58e06f15b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 35cee598fb119a12248f3499078d0a7af827a471..273c7e2b75b8b491d6c56b1fc7146584c0a8a14c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2065,6 +2061,7 @@ You can find examples of how to migrate existing forms to form builder <a href="
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2129,10 +2126,10 @@ You can find examples of how to migrate existing forms to form builder <a href="
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 58edfe19c4e94d17b44638b9ef461c022069562e..dd3f1816459800e19c46f770d1c1b315355f13a1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1909,6 +1905,7 @@ Have a <a href="https://github.com/WoltLab/WCF/blob/ce163806c468763f6e3b04e4bf73
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1973,10 +1970,10 @@ Have a <a href="https://github.com/WoltLab/WCF/blob/ce163806c468763f6e3b04e4bf73
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9f51941455ad07f2f967fb21ac083c294a91f03b..b0af336b6c2016474053d71c0d675d44695b6267 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2041,6 +2037,7 @@ The usual pattern of creating a thumbnail for an existing image would then look
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2105,10 +2102,10 @@ The usual pattern of creating a thumbnail for an existing image would then look
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 2e9a84a29f5358f7a2063bfbdd068864881c53c7..612d08db803e78c9e17c8cd29bde0e501182b891 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2014,6 +2010,7 @@ We unified the approach for such links:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2078,10 +2075,10 @@ We unified the approach for such links:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index cde8f400cc6abe3dfa15dce57c85edaf9dadd558..52a194094cfe6f3ec3e4328980c852dfdf7f17c2 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1909,27 +1905,27 @@ Additionally, we have added a new <code>{objectAction}</code> template plugin, w
    These elements can be generated with the <code>{objectAction}</code> template plugin for the <code>delete</code> and <code>toggle</code> action.</li>
 </ol>
 <p>Example:</p>
-<div class="highlight"><pre><span></span><code>&lt;table class=&quot;table jsObjectActionContainer&quot; {*
-    *}data-object-action-class-name=&quot;wcf\data\foo\FooAction&quot;&gt;
-    &lt;thead&gt;
-        &lt;tr&gt;
-            {* … *}
-        &lt;/tr&gt;
-    &lt;/thead&gt;
-
-    &lt;tbody&gt;
-        {foreach from=$objects item=foo}
-            &lt;tr class=&quot;jsObjectActionObject&quot; data-object-id=&quot;{@$foo-&gt;getObjectID()}&quot;&gt;
-                &lt;td class=&quot;columnIcon&quot;&gt;
-                    {objectAction action=&quot;toggle&quot; isDisabled=$foo-&gt;isDisabled}
-                    {objectAction action=&quot;delete&quot; objectTitle=$foo-&gt;getTitle()}
-                    {* … *}
-                &lt;/td&gt;
-                {* … *}
-            &lt;/tr&gt;
-        {/foreach}
-    &lt;/tbody&gt;
-&lt;/table&gt;
+<div class="highlight"><pre><span></span><code><span class="x">&lt;table class=&quot;table jsObjectActionContainer&quot; </span><span class="cp">{</span><span class="c">*</span>
+<span class="c">    *</span><span class="cp">}</span><span class="x">data-object-action-class-name=&quot;wcf\data\foo\FooAction&quot;&gt;</span>
+<span class="x">    &lt;thead&gt;</span>
+<span class="x">        &lt;tr&gt;</span>
+<span class="x">            </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">        &lt;/tr&gt;</span>
+<span class="x">    &lt;/thead&gt;</span>
+
+<span class="x">    &lt;tbody&gt;</span>
+<span class="x">        </span><span class="cp">{</span><span class="nf">foreach</span> <span class="na">from</span><span class="o">=</span><span class="nv">$objects</span> <span class="na">item</span><span class="o">=</span><span class="na">foo</span><span class="cp">}</span><span class="x"></span>
+<span class="x">            &lt;tr class=&quot;jsObjectActionObject&quot; data-object-id=&quot;</span><span class="cp">{</span><span class="o">@</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">getObjectID</span><span class="o">()</span><span class="cp">}</span><span class="x">&quot;&gt;</span>
+<span class="x">                &lt;td class=&quot;columnIcon&quot;&gt;</span>
+<span class="x">                    </span><span class="cp">{</span><span class="nf">objectAction</span> <span class="na">action</span><span class="o">=</span><span class="s2">&quot;toggle&quot;</span> <span class="na">isDisabled</span><span class="o">=</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">isDisabled</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                    </span><span class="cp">{</span><span class="nf">objectAction</span> <span class="na">action</span><span class="o">=</span><span class="s2">&quot;delete&quot;</span> <span class="na">objectTitle</span><span class="o">=</span><span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">getTitle</span><span class="o">()</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                    </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">                &lt;/td&gt;</span>
+<span class="x">                </span><span class="cp">{</span><span class="c">* … *</span><span class="cp">}</span><span class="x"></span>
+<span class="x">            &lt;/tr&gt;</span>
+<span class="x">        </span><span class="cp">{</span><span class="nf">/foreach</span><span class="cp">}</span><span class="x"></span>
+<span class="x">    &lt;/tbody&gt;</span>
+<span class="x">&lt;/table&gt;</span>
 </code></pre></div>
 <p>Please refer to the documentation in <a href="https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/template/plugin/ObjectActionFunctionTemplatePlugin.class.php"><code>ObjectActionFunctionTemplatePlugin</code></a> for details and examples on how to use this template plugin.</p>
 <p>The relevant TypeScript module registering the event listeners on the object action buttons is <a href="https://github.com/WoltLab/WCF/blob/master/ts/WoltLabSuite/Core/Ui/Object/Action.ts"><code>Ui/Object/Action</code></a>.
@@ -1983,7 +1979,7 @@ To also cover scenarios in which there are fixed child elements that should not
 <div class="md-source-date">
   <small>
     
-      Last update: 2021-03-24
+      Last update: 2021-04-06
     
   </small>
 </div>
@@ -1998,6 +1994,7 @@ To also cover scenarios in which there are fixed child elements that should not
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2062,10 +2059,10 @@ To also cover scenarios in which there are fixed child elements that should not
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5bbf07aacfd9fe20772c0cc67415b793ec845da5..6cdd0a387b1af2539ef3727dd816f2cb3bf7cdc3 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1962,6 +1958,7 @@ Refer to <a href="https://paragonie.com/blog/2016/06/constant-time-encoding-bori
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2026,10 +2023,10 @@ Refer to <a href="https://paragonie.com/blog/2016/06/constant-time-encoding-bori
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a3d03fdf06a11f7b04afaccda1c76dd9e6ca07e8..ca13e189cdc3f7c74ad8e52c0895b3d75ddd77a8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2080,6 +2076,7 @@ If your custom <code>IUserAvatar</code> implementation supports image types with
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2144,10 +2141,10 @@ If your custom <code>IUserAvatar</code> implementation supports image types with
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 012595fcf1a16aaf08492a9d17a1becf61b86ef2..e601bc0022f175d11d360d2f68c2715d7cde27ef 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2294,6 +2290,7 @@ The <code>{csrfToken}</code> tag is a drop-in replacement and was backported to
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2358,10 +2355,10 @@ The <code>{csrfToken}</code> tag is a drop-in replacement and was backported to
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c0de283c4aa5823bba2c8fb97bf78c8e810ab766..45dcbfeaaf15be68a9673f870ac42e649f37b18b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1895,6 +1891,7 @@ See <a href="https://github.com/WoltLab/WCF/pull/3612">WoltLab/WCF#3612</a> for
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1959,10 +1956,10 @@ See <a href="https://github.com/WoltLab/WCF/pull/3612">WoltLab/WCF#3612</a> for
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a28b11b36828257a83efc0ff286bc2836d4677c4..7636df7ab30ad179290853d0b0c910336df3176b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2022,6 +2018,7 @@ If it is not present, <code>DatabaseTable::indices()</code> will automatically s
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2086,10 +2083,10 @@ If it is not present, <code>DatabaseTable::indices()</code> will automatically s
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5ef3a4d9662399de23d4b18d2bce16a8427dacda..8e672484e2df988c7d785faa8431d81bc994c2de 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2482,6 +2478,7 @@ Instead of using a dummy <code>&lt;instruction&gt;</code> that idempotently upda
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2546,10 +2543,10 @@ Instead of using a dummy <code>&lt;instruction&gt;</code> that idempotently upda
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a33366223b670a0ad9ea2a63b1f856ffc41c0d42..4dfebdbd158547619be40e11099d9c1ad88620ba 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a17cd1c75cd8fae585ea27ab195b97f5c379ff25..7f5b993223600ed174ad91027718963deb86701f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2055,6 +2051,7 @@ the full external link otherwise.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2119,10 +2116,10 @@ the full external link otherwise.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index e13589d25d23d511e3c45972a73a8b1b2db80e3c..af19b8581b3d341876507abaabe1b3c669d12401 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1955,6 +1951,7 @@ the class has to implement the <code>wcf\system\search\acp\IACPSearchResultProvi
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2019,10 +2016,10 @@ the class has to implement the <code>wcf\system\search\acp\IACPSearchResultProvi
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5df9b81f2224498d60af19762bc45ee16289b882..a1ed9fe23246795923037bd81f57cf8047ee87ec 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1962,6 +1958,7 @@ If no <code>application</code> attribute is given, the following rules are appli
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2026,10 +2023,10 @@ If no <code>application</code> attribute is given, the following rules are appli
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c39dfa66a0826a676ea32dbc1c88b527430d6c34..8cbaa8eae969bba15229d8f4064c7ca926a0eda5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2169,6 +2165,7 @@ The <code>name</code> attribute is a 0-indexed integer.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2233,10 +2230,10 @@ The <code>name</code> attribute is a 0-indexed integer.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 905d323825c24a987a00fbce17a72c6700201763..11bfe14dea91acf85d436174d99bc4b39a676155 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0e3b5e82595c57b7c6a7133a59bbbd9f80ec6dca..b7b2ab8481c93230a602234d024ee76e1bc74a04 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1988,6 +1984,7 @@ The class has to implement the <code>wcf\system\clipboard\action\IClipboardActio
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2052,10 +2049,10 @@ The class has to implement the <code>wcf\system\clipboard\action\IClipboardActio
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6858653525159f8261aba65cd7c38fe66d90af1b..c293a9a5f6665c5140d342d7c090e880fea2c194 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index d02f42b925af6b8ffd7a0c49285e079979b266c8..147115e1e9ee05bdb5523221119d4b8825716679 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2033,6 +2029,7 @@ They correspond to the fields in <code>crontab(5)</code> of a cron daemon and ac
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2097,10 +2094,10 @@ They correspond to the fields in <code>crontab(5)</code> of a cron daemon and ac
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 680e9d23f74665073b15ae7e470cba35015269c9..88cabc337f7916345eda7beb3c24deb226affe81 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1907,6 +1903,7 @@ If you're updating from <code>1.0.0</code> to <code>1.0.1</code>, <code>&lt;targ
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1938,10 +1935,10 @@ If you're updating from <code>1.0.0</code> to <code>1.0.1</code>, <code>&lt;targ
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f332a7541c952844736fd470c30c743098ba250c..6679fca28679ba40af9380f870da3610f87e8178 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2078,6 +2074,7 @@ If the nice value of two event listeners is equal, they are sorted by the listen
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2142,10 +2139,10 @@ If the nice value of two event listeners is equal, they are sorted by the listen
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1d569718df5cac525ab358917c15c1f6a196d415..80aec3b2f96b0bc0bcd0aeebf4629ae06c950e21 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1915,6 +1911,7 @@ The file path given in the <code>instruction</code> element as its value must be
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1979,10 +1976,10 @@ The file path given in the <code>instruction</code> element as its value must be
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 8a11eaeea0f4925585347bcf16c1dc5f4efcfd9e..cbe0af02dd5dd137558e54ad4366629c0b4c3284 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3ad76f7cc88036cdffe0f33b9ebce7542b5472e5..ed31142a3d769ef27b70a5681f913e7e306bad68 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6fb9b3ba56b13caa1ed45d06d891071d30a3c60c..0ad3fac4c240f2b545b006348ca638ff98df513b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 875cc76bdbd3b02f91e4887b32ec571e07b0f809..2653064648d4c8655178b802ba8a0b8e09bc3c1c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index af3c82859d41fedd021e7fc61fa06f01e1f46974..08e9696f83cb7c6a099053dbec43d80c0f5e4bf1 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 596a9f3b4f4e166e0dc02b79aace0f56da8acd75..8a2f9472238d4f8bf5cc88d425d8f886be4cc72c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1945,6 +1941,7 @@ Posts are then registered as an object type, implementing the “taggable conten
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2009,10 +2006,10 @@ Posts are then registered as an object type, implementing the “taggable conten
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index c8d6c40e729b424b1559520ef562820f072ad5df..4b5f9338e5bfa1ec10bf1d4a19b27fdc7caeece7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1975,6 +1971,7 @@ Refer to the documentation of these for further explanation.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2039,10 +2036,10 @@ Refer to the documentation of these for further explanation.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1945e20e47ba437979cfe653632bc28c510e95a4..7b476b573541c1115a0757108eb70298acd10b80 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2314,6 +2310,7 @@ DESC:wcf.global.sortOrder.descending<span class="nt">&lt;/selectoptions&gt;</spa
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2378,10 +2375,10 @@ DESC:wcf.global.sortOrder.descending<span class="nt">&lt;/selectoptions&gt;</spa
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 116dc2c1792c61e101e6ed51ca74630aa3fdb2aa..721e94828a4db40e9729ad37c0deb64bab01e9eb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2198,6 +2194,7 @@ page would be presented with a permission denied message.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2262,10 +2259,10 @@ page would be presented with a permission denied message.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 4d5ecc1b9cb1ea4842ea48a48d0191bbeed5227d..424c6ad17d301ec94efb9e4ec0a2fd0d68e2d6cd 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dc21b07afbdd61ef4d07721b69303961ccd7274e..2308d7ccb0bdd8111d980c9859c992b5e2173ceb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1987,6 +1983,7 @@ This grants you access to the class members, including <code>$this-&gt;installat
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2051,10 +2048,10 @@ This grants you access to the class members, including <code>$this-&gt;installat
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b4f921f0066af0549c98239393fa06e138653eec..d44aa12b9d15749ddebb5b15a2661ee73c159f4c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1995,6 +1991,7 @@ Aliases must be separated by a line feed character (<code>\n</code>, U+000A).</p
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2059,10 +2056,10 @@ Aliases must be separated by a line feed character (<code>\n</code>, U+000A).</p
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 78537cafb49848733ba633fe69d4492803bc2764..216925e2d47e3dfe0de7a7990947e868114fa7b8 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2063,6 +2059,7 @@ If you really need triggers, you should consider adding them by custom SQL queri
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2127,10 +2124,10 @@ If you really need triggers, you should consider adding them by custom SQL queri
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b22f8a30ef88e6220eb18bf866f7312cf4d83e9a..9a4676fa6c94ba806bd2295937641ac675a6b589 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1887,6 +1883,7 @@ Please use the ACP's export mechanism to export styles.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1951,10 +1948,10 @@ Please use the ACP's export mechanism to export styles.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b42529d5d16afdc938abfb0a2c50c217c728ab43..32cd5bbc0e6276222fd9ccd8cda7d60fd1a67c7f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2058,6 +2054,7 @@ If the nice value of two template listeners is equal, the order is undefined.</p
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2122,10 +2119,10 @@ If the nice value of two template listeners is equal, the order is undefined.</p
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0a1dad3223b5ac5203b2b01e3e1f5da7bde28b7a..258f540566b6516f1b1332b64d9ee9e9f910151b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 36c4d02dfc255164700e31c1df6fd6deba9cadae..3529a648ca9d640ea28f3930d889ee2c1feb4c3b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2031,6 +2027,7 @@ If you want to provide an optional description of the option, you have to provid
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2095,10 +2092,10 @@ If you want to provide an optional description of the option, you have to provid
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6bfa175e84c49e8440e1a7b4cb2b17f19a8ae535..c5ca6ed9c46cf9a3a6e535940264ddfaf1954a66 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2072,6 +2068,7 @@ the class has to implement the <code>wcf\system\menu\user\IUserMenuItemProvider<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2136,10 +2133,10 @@ the class has to implement the <code>wcf\system\menu\user\IUserMenuItemProvider<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9c62148d00bb08ae9c25bf28818bfe4fceed5793..e48dc131b11a4f39b17f385d84af043240724123 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2030,6 +2026,7 @@ Defines whether this type of email notifications is enabled by default.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2094,10 +2091,10 @@ Defines whether this type of email notifications is enabled by default.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index dcab598a0e2506bc96659ea57d4e4d978cbb8af3..328bcac12b9c06fd71bf18806588a0c0c143dd8a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2122,6 +2118,7 @@ If you want to provide an optional description of the option, you have to provid
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2186,10 +2183,10 @@ If you want to provide an optional description of the option, you have to provid
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9307b3baedd679ab4d19fff0d735775698f05aa2..402d90d93fb9beed57f4c460be5419600a8d7e4a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1990,6 +1986,7 @@ the class has to implement the <code>wcf\system\menu\user\profile\content\IUserP
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2054,10 +2051,10 @@ the class has to implement the <code>wcf\system\menu\user\profile\content\IUserP
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 34e984a7e59f26c1cc74502fbef8597b4f8ffb1b..dafda9ecce4fab7bd06df500b14299ada2a06bfc 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1990,6 +1986,7 @@ at least how it was implemented - was really necessary.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2054,10 +2051,10 @@ at least how it was implemented - was really necessary.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index bb4971566ffecb448cb7cfe14b0c93366afa172f..7d8ad4354cc2b8f09d65e4d6bd76a06aa309b691 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1940,6 +1936,7 @@ rebuild for whatever reason.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1971,10 +1968,10 @@ rebuild for whatever reason.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 544c37304849a86b38de00d7df12894ac2f2f636..eacd337d51f695edcb66fd16bbee1163ef8961be 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1918,6 +1914,7 @@ In most instances, you only need to set the <code>AbstractRuntimeCache::$listCla
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1949,10 +1946,10 @@ In most instances, you only need to set the <code>AbstractRuntimeCache::$listCla
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 60f8d8bb54b8828f7a07fa9a7f91f645b4761f23..a2922e36f4e9390939cd6b178bc5025a8224b46a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index a643076e4b58d58926c0064838d0e1643af3f89c..99011acb1c9c3d0c106f9ab449dd850b27fef896 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1957,6 +1953,7 @@ EOT
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2021,10 +2018,10 @@ EOT
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f6fae1dcd05a29cf96fff32f1224597e5ee3aa9f..8a7fa1390e9f28c1066a8236008eb348f7c51d29 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 67a1ea6fa31f6633e330e3ca8abfc47f446d519d..04be273b6dced4fdd5bcab8569fa739f62371622 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2182,6 +2178,7 @@ Furthermore, the type-hinting of the parameter illustrates in which contexts the
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2246,10 +2243,10 @@ Furthermore, the type-hinting of the parameter illustrates in which contexts the
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5a939d23d27ca704b4fbc111b0cd5dc7503c74ec..1adb0479ad7a32379dbd074de1c9eea2a08c6849 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1999,6 +1995,7 @@ Every form container has to create a matching form container dependency object f
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2063,10 +2060,10 @@ Every form container has to create a matching form container dependency object f
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 40fe6da9c8c9802730addb10cb296fd3570642c9..fc936439e228fc291b625741e62905dbfa8f151a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -3032,6 +3028,7 @@ This trait provides <code>getWysiwygId()</code> and <code>wysiwygId($wysiwygId)<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -3096,10 +3093,10 @@ This trait provides <code>getWysiwygId()</code> and <code>wysiwygId($wysiwygId)<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 8991e1ef7dab50bdd992eafdfcf46643dbf7e131..c02853060f03cab424b676b6b20cd59e1a4c7645 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2027,6 +2023,7 @@ The constructor of <code>FormBuilderDialog</code> expects the following paramete
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2091,10 +2088,10 @@ The constructor of <code>FormBuilderDialog</code> expects the following paramete
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0be00f7c29487f3d48ca2122f7dac0407d97747c..ee96e02aa63e75e47ce17891665f7336d81aa0c5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2753,6 +2749,7 @@ The implementing class has to implement the methods <code>suffix($languageItem =
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2817,10 +2814,10 @@ The implementing class has to implement the methods <code>suffix($languageItem =
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0803f44e146dc85ddccd7069be46a2ade6b9c510..d0643db16b8da56f36befa39a72dfe5ffffd7ecb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2099,6 +2095,7 @@ When the data processor is invoked, it checks whether the relevant entry in the
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2163,10 +2160,10 @@ When the data processor is invoked, it checks whether the relevant entry in the
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 3e24f852d2c7f038440ac51f31a5824f94feb862..e47f465a7974923d91a8218ea859079f8a0c2c58 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2281,6 +2277,7 @@ This allows options to store arbitrary data which can be accessed but were not i
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2345,10 +2342,10 @@ This allows options to store arbitrary data which can be accessed but were not i
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5659e77644493978fe426e9957e991ef8d80bc3f..b8584cc4f400fc6226f6f4c12cf64cc9e63eeeb7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1914,6 +1910,7 @@ The language variable follows the pattern <code>wcf.acp.sitemap.objectType.{obje
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1978,10 +1975,10 @@ The language variable follows the pattern <code>wcf.acp.sitemap.objectType.{obje
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 4712a642f506472ee96e73c18b347b6d7f41ab00..f0e74309bfb7706210387cf45e65cbddcbcfae0a 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1867,6 +1863,7 @@ For this form and the user activity point list shown in the frontend, you have t
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1931,10 +1928,10 @@ For this form and the user activity point list shown in the frontend, you have t
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0ea98f61962bf6d9cdc59281e7ec3bd43b607cc3..f7669139ac311d574ffd32d7ac318cd15afa0254 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2240,6 +2236,7 @@ In this case, you can use <code>UserNotificationHandler::markAsConfirmed()</code
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2304,10 +2301,10 @@ In this case, you can use <code>UserNotificationHandler::markAsConfirmed()</code
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9fc9166667ff11940b35f0eedb0c731f7826214f..9465a55421960c5e64915d15ff087a17297e446b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2101,6 +2097,7 @@ it includes everything that is required for a basic app.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2165,10 +2162,10 @@ it includes everything that is required for a basic app.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 9c80e6f90f7f53d303e3bf55a51d0e7057304027..2e52ec4e0d4c8448afb5d157d569d0f62ebf3923 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2001,6 +1997,7 @@ The third line tells the IDE (if <code>@mixin</code> is supported) that the data
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2032,10 +2029,10 @@ The third line tells the IDE (if <code>@mixin</code> is supported) that the data
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index f30fa53513b11d51dc0de6474f4b82d70e22e6b8..41c11e7e139258ca79f83074cf70ac5731c67dfb 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2207,6 +2203,7 @@ Therefore, the line must be split into multiple lines with each argument in a se
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2271,10 +2268,10 @@ Therefore, the line must be split into multiple lines with each argument in a se
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 6fa1352c9316917a9317f74f1c2bc68ec66fad84..3141d8545181d9209aa551201038e84adf4c8c59 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2134,6 +2130,7 @@ This case is covered by <code>PreparedStatement::fetchMap()</code>:</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2198,10 +2195,10 @@ This case is covered by <code>PreparedStatement::fetchMap()</code>:</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 85cee9c3ca77ea2b7596de438f9bc96f706f1637..d3236f67ef6545195fbe8a751034a2007f8afdaf 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2314,6 +2310,7 @@ This can be achieved by setting the <code>$objectClassName</code> property to th
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2378,10 +2375,10 @@ This can be achieved by setting the <code>$objectClassName</code> property to th
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 91422f345a5c541103ee8be4874ff998929d1a04..2910079322ed922394788af1cf0c59644ad02a6b 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1943,6 +1939,7 @@ All of the exceptions are found in the <code>wcf\system\exception</code> namespa
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2007,10 +2004,10 @@ All of the exceptions are found in the <code>wcf\system\exception</code> namespa
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index bf881986203cb2f902ba32efd1a85f813181bc23..cd8e6710b271b64ca05f2c2704676f647c5e97e5 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2094,6 +2090,7 @@ because it does not contain personal data, such as internal data.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2158,10 +2155,10 @@ because it does not contain personal data, such as internal data.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 5fc2cb0cdc03645df01a070e7c9e4c40767930b0..9a60ab0787af74cb9e30e9014901f7cb7242e717 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
             </article>
           </div>
         </div>
+        
       </main>
       
         
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 32cf8ab868d108f0ef4414a3a05e81f002314549..41151547fb17df4ef54701d36eed57745dbe4d27 100644 (file)
@@ -1 +1 @@
-{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"WoltLab Suite 5.4 Documentation # Introduction # This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more. About WoltLab Suite # WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"WoltLab Suite 5.4 Documentation"},{"location":"#woltlab-suite-54-documentation","text":"","title":"WoltLab Suite 5.4 Documentation"},{"location":"#introduction","text":"This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more.","title":"Introduction"},{"location":"#about-woltlab-suite","text":"WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"About WoltLab Suite"},{"location":"getting-started/","text":"Creating a simple package # Setup and Requirements # This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows The package.xml File # We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is. The PHP Class # The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all. The Template # Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance. The Page Definition # The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus. Building the Package # If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition. Installation # Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package! Developer Tools # This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed. Registering a Project # Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml . Synchronizing # The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed. Appendix # Template Guessing # The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Getting Started"},{"location":"getting-started/#creating-a-simple-package","text":"","title":"Creating a simple package"},{"location":"getting-started/#setup-and-requirements","text":"This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows","title":"Setup and Requirements"},{"location":"getting-started/#the-packagexml-file","text":"We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is.","title":"The package.xml File"},{"location":"getting-started/#the-php-class","text":"The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all.","title":"The PHP Class"},{"location":"getting-started/#the-template","text":"Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance.","title":"The Template"},{"location":"getting-started/#the-page-definition","text":"The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus.","title":"The Page Definition"},{"location":"getting-started/#building-the-package","text":"If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition.","title":"Building the Package"},{"location":"getting-started/#installation","text":"Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package!","title":"Installation"},{"location":"getting-started/#developer-tools","text":"This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed.","title":"Developer Tools"},{"location":"getting-started/#registering-a-project","text":"Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml .","title":"Registering a Project"},{"location":"getting-started/#synchronizing","text":"The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed.","title":"Synchronizing"},{"location":"getting-started/#appendix","text":"","title":"Appendix"},{"location":"getting-started/#template-guessing","text":"The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Template Guessing"},{"location":"javascript/code-snippets/","text":"Code Snippets - JavaScript API # This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function. ImageViewer # The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"Code Snippets"},{"location":"javascript/code-snippets/#code-snippets-javascript-api","text":"This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function.","title":"Code Snippets - JavaScript API"},{"location":"javascript/code-snippets/#imageviewer","text":"The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"ImageViewer"},{"location":"javascript/general-usage/","text":"General JavaScript Usage # The History of the Legacy API # The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base. Embedding JavaScript inside Templates # The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script > Including External JavaScript Files # The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about. Debug-Variants and Cache-Buster # Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The Accelerated Guest View (\"Tiny Builds\") # You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The {js} Template Plugin # The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"General Usage"},{"location":"javascript/general-usage/#general-javascript-usage","text":"","title":"General JavaScript Usage"},{"location":"javascript/general-usage/#the-history-of-the-legacy-api","text":"The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base.","title":"The History of the Legacy API"},{"location":"javascript/general-usage/#embedding-javascript-inside-templates","text":"The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script >","title":"Embedding JavaScript inside Templates"},{"location":"javascript/general-usage/#including-external-javascript-files","text":"The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about.","title":"Including External JavaScript Files"},{"location":"javascript/general-usage/#debug-variants-and-cache-buster","text":"Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"Debug-Variants and Cache-Buster"},{"location":"javascript/general-usage/#the-accelerated-guest-view-tiny-builds","text":"You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"The Accelerated Guest View (\"Tiny Builds\")"},{"location":"javascript/general-usage/#the-js-template-plugin","text":"The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"The {js} Template Plugin"},{"location":"javascript/helper-functions/","text":"JavaScript Helper Functions # Introduction # Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction. Elements # elCreate(tagName: string): Element # Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" ); elRemove(element: Element) # Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element ); elShow(element: Element) # Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" ); elHide(element: Element) # Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" ); elToggle(element: Element) # Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() . Attributes # elAttr(element: Element, attribute: string, value?: string): string # Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" ); elAttrBool(element: Element, attribute: string): boolean # Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish } elData(element: Element, attribute: string, value?: string): string # Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" ); elDataBool(element: Element, attribute: string): boolean # Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish } Selecting Elements # Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error. elById(id: string): Element | null # Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" ); elBySel(selector: string, context?: Element): Element | null # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" ); elBySelAll(selector: string, context?: Element, callback: (element: Element) => void): NodeList # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" ); Callback to Iterate Over Elements # elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element }); elClosest(element: Element, selector: string): Element | null # Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" ); Text Nodes # If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector. elByClass(className: string, context?: Element): NodeList # Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" ); elByTag(tagName: string, context?: Element): NodeList # Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" ); Utility Functions # `elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null`` # Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function. String Extensions # hashCode(): string # Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"Helper Functions"},{"location":"javascript/helper-functions/#javascript-helper-functions","text":"","title":"JavaScript Helper Functions"},{"location":"javascript/helper-functions/#introduction","text":"Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction.","title":"Introduction"},{"location":"javascript/helper-functions/#elements","text":"","title":"Elements"},{"location":"javascript/helper-functions/#elcreatetagname-string-element","text":"Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" );","title":"elCreate(tagName: string): Element"},{"location":"javascript/helper-functions/#elremoveelement-element","text":"Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element );","title":"elRemove(element: Element)"},{"location":"javascript/helper-functions/#elshowelement-element","text":"Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" );","title":"elShow(element: Element)"},{"location":"javascript/helper-functions/#elhideelement-element","text":"Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" );","title":"elHide(element: Element)"},{"location":"javascript/helper-functions/#eltoggleelement-element","text":"Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() .","title":"elToggle(element: Element)"},{"location":"javascript/helper-functions/#attributes","text":"","title":"Attributes"},{"location":"javascript/helper-functions/#elattrelement-element-attribute-string-value-string-string","text":"Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" );","title":"elAttr(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#elattrboolelement-element-attribute-string-boolean","text":"Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish }","title":"elAttrBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#eldataelement-element-attribute-string-value-string-string","text":"Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" );","title":"elData(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#eldataboolelement-element-attribute-string-boolean","text":"Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish }","title":"elDataBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#selecting-elements","text":"Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error.","title":"Selecting Elements"},{"location":"javascript/helper-functions/#elbyidid-string-element-null","text":"Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" );","title":"elById(id: string): Element | null"},{"location":"javascript/helper-functions/#elbyselselector-string-context-element-element-null","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" );","title":"elBySel(selector: string, context?: Element): Element | null"},{"location":"javascript/helper-functions/#elbyselallselector-string-context-element-callback-element-element-void-nodelist","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" );","title":"elBySelAll(selector: string, context?: Element, callback: (element: Element) =&gt; void): NodeList"},{"location":"javascript/helper-functions/#callback-to-iterate-over-elements","text":"elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element });","title":"Callback to Iterate Over Elements"},{"location":"javascript/helper-functions/#elclosestelement-element-selector-string-element-null","text":"Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" );","title":"elClosest(element: Element, selector: string): Element | null"},{"location":"javascript/helper-functions/#text-nodes","text":"If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector.","title":"Text Nodes"},{"location":"javascript/helper-functions/#elbyclassclassname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" );","title":"elByClass(className: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#elbytagtagname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" );","title":"elByTag(tagName: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#utility-functions","text":"","title":"Utility Functions"},{"location":"javascript/helper-functions/#elinnererrorelement-element-errormessage-string-ishtml-boolean-element-null","text":"Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function.","title":"`elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null``"},{"location":"javascript/helper-functions/#string-extensions","text":"","title":"String Extensions"},{"location":"javascript/helper-functions/#hashcode-string","text":"Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"hashCode(): string"},{"location":"javascript/legacy-api/","text":"Legacy JavaScript API # Introduction # The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API. Classes # Singletons # Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; }); Regular Classes # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); Inheritance # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); Ajax Requests # // App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; }); Phrases # < script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script > Event-Listener # < script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Legacy API"},{"location":"javascript/legacy-api/#legacy-javascript-api","text":"","title":"Legacy JavaScript API"},{"location":"javascript/legacy-api/#introduction","text":"The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API.","title":"Introduction"},{"location":"javascript/legacy-api/#classes","text":"","title":"Classes"},{"location":"javascript/legacy-api/#singletons","text":"Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; });","title":"Singletons"},{"location":"javascript/legacy-api/#regular-classes","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; });","title":"Regular Classes"},{"location":"javascript/legacy-api/#inheritance","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"Inheritance"},{"location":"javascript/legacy-api/#ajax-requests","text":"// App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; });","title":"Ajax Requests"},{"location":"javascript/legacy-api/#phrases","text":"< script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script >","title":"Phrases"},{"location":"javascript/legacy-api/#event-listener","text":"< script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Event-Listener"},{"location":"javascript/new-api_ajax/","text":"Ajax Requests - JavaScript API # Ajax inside Modules # The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed. _ajaxSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; }); Request Settings # The object returned by the aforementioned _ajaxSetup() callback can contain these values: data # Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters . contentType # Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty. responseType # Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess ! type # Defaults to POST . The HTTP Verb used for this request. url # Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true. withCredentials # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint. autoAbort # Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code. ignoreError # Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback. silent # Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator. includeRequestedWith # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint. failure # Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON. finalize # Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest . success # Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued. _ajaxSuccess() # This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests. _ajaxFailure() # Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed. Single Requests Without a Module # The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Ajax"},{"location":"javascript/new-api_ajax/#ajax-requests-javascript-api","text":"","title":"Ajax Requests - JavaScript API"},{"location":"javascript/new-api_ajax/#ajax-inside-modules","text":"The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed.","title":"Ajax inside Modules"},{"location":"javascript/new-api_ajax/#_ajaxsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; });","title":"_ajaxSetup()"},{"location":"javascript/new-api_ajax/#request-settings","text":"The object returned by the aforementioned _ajaxSetup() callback can contain these values:","title":"Request Settings"},{"location":"javascript/new-api_ajax/#data","text":"Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters .","title":"data"},{"location":"javascript/new-api_ajax/#contenttype","text":"Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty.","title":"contentType"},{"location":"javascript/new-api_ajax/#responsetype","text":"Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess !","title":"responseType"},{"location":"javascript/new-api_ajax/#type","text":"Defaults to POST . The HTTP Verb used for this request.","title":"type"},{"location":"javascript/new-api_ajax/#url","text":"Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true.","title":"url"},{"location":"javascript/new-api_ajax/#withcredentials","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint.","title":"withCredentials"},{"location":"javascript/new-api_ajax/#autoabort","text":"Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code.","title":"autoAbort"},{"location":"javascript/new-api_ajax/#ignoreerror","text":"Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback.","title":"ignoreError"},{"location":"javascript/new-api_ajax/#silent","text":"Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator.","title":"silent"},{"location":"javascript/new-api_ajax/#includerequestedwith","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint.","title":"includeRequestedWith"},{"location":"javascript/new-api_ajax/#failure","text":"Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON.","title":"failure"},{"location":"javascript/new-api_ajax/#finalize","text":"Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest .","title":"finalize"},{"location":"javascript/new-api_ajax/#success","text":"Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued.","title":"success"},{"location":"javascript/new-api_ajax/#_ajaxsuccess","text":"This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests.","title":"_ajaxSuccess()"},{"location":"javascript/new-api_ajax/#_ajaxfailure","text":"Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed.","title":"_ajaxFailure()"},{"location":"javascript/new-api_ajax/#single-requests-without-a-module","text":"The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Single Requests Without a Module"},{"location":"javascript/new-api_browser/","text":"Browser and Screen Sizes - JavaScript API # Ui/Screen # CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle. Supported Aliases # You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px) on(query: string, callbacks: Object): string # Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() . remove(query: string, uuid: string) # Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() . is(query: string): boolean # Tests if the provided media query currently matches and returns true on match. scrollDisable() # Temporarily prevents the page from being scrolled, until .scrollEnable() is called. scrollEnable() # Enables page scrolling again, unless another pending action has also prevented the page scrolling. Environment # The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks. browser(): string # Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default) platform(): string # Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"Browser and Screen Sizes"},{"location":"javascript/new-api_browser/#browser-and-screen-sizes-javascript-api","text":"","title":"Browser and Screen Sizes - JavaScript API"},{"location":"javascript/new-api_browser/#uiscreen","text":"CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle.","title":"Ui/Screen"},{"location":"javascript/new-api_browser/#supported-aliases","text":"You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px)","title":"Supported Aliases"},{"location":"javascript/new-api_browser/#onquery-string-callbacks-object-string","text":"Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() .","title":"on(query: string, callbacks: Object): string"},{"location":"javascript/new-api_browser/#removequery-string-uuid-string","text":"Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() .","title":"remove(query: string, uuid: string)"},{"location":"javascript/new-api_browser/#isquery-string-boolean","text":"Tests if the provided media query currently matches and returns true on match.","title":"is(query: string): boolean"},{"location":"javascript/new-api_browser/#scrolldisable","text":"Temporarily prevents the page from being scrolled, until .scrollEnable() is called.","title":"scrollDisable()"},{"location":"javascript/new-api_browser/#scrollenable","text":"Enables page scrolling again, unless another pending action has also prevented the page scrolling.","title":"scrollEnable()"},{"location":"javascript/new-api_browser/#environment","text":"The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks.","title":"Environment"},{"location":"javascript/new-api_browser/#browser-string","text":"Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default)","title":"browser(): string"},{"location":"javascript/new-api_browser/#platform-string","text":"Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"platform(): string"},{"location":"javascript/new-api_core/","text":"Core Modules and Functions - JavaScript API # A brief overview of common methods that may be useful when writing any module. Core # clone(object: Object): Object # Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true }); extend(base: Object, ...merge: Object[]): Object # Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false }); inherit(base: Object, target: Object, merge?: Object) # Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); isPlainObject(object: Object): boolean # Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false }); triggerEvent(element: Element, eventName: string) # Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); }); Language # add(key: string, value: string) # Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script > addObject(object: Object) # Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script > get(key: string, parameters?: Object): string # Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); }); StringUtil # escapeHTML(str: string): string # Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt; escapeRegExp(str: string): string # Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions. lcfirst(str: string): string # Makes a string's first character lowercase. ucfirst(str: string): string # Makes a string's first character uppercase. unescapeHTML(str: string): string # Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"Core Functions"},{"location":"javascript/new-api_core/#core-modules-and-functions-javascript-api","text":"A brief overview of common methods that may be useful when writing any module.","title":"Core Modules and Functions - JavaScript API"},{"location":"javascript/new-api_core/#core","text":"","title":"Core"},{"location":"javascript/new-api_core/#cloneobject-object-object","text":"Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true });","title":"clone(object: Object): Object"},{"location":"javascript/new-api_core/#extendbase-object-merge-object-object","text":"Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false });","title":"extend(base: Object, ...merge: Object[]): Object"},{"location":"javascript/new-api_core/#inheritbase-object-target-object-merge-object","text":"Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"inherit(base: Object, target: Object, merge?: Object)"},{"location":"javascript/new-api_core/#isplainobjectobject-object-boolean","text":"Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false });","title":"isPlainObject(object: Object): boolean"},{"location":"javascript/new-api_core/#triggereventelement-element-eventname-string","text":"Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); });","title":"triggerEvent(element: Element, eventName: string)"},{"location":"javascript/new-api_core/#language","text":"","title":"Language"},{"location":"javascript/new-api_core/#addkey-string-value-string","text":"Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script >","title":"add(key: string, value: string)"},{"location":"javascript/new-api_core/#addobjectobject-object","text":"Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script >","title":"addObject(object: Object)"},{"location":"javascript/new-api_core/#getkey-string-parameters-object-string","text":"Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); });","title":"get(key: string, parameters?: Object): string"},{"location":"javascript/new-api_core/#stringutil","text":"","title":"StringUtil"},{"location":"javascript/new-api_core/#escapehtmlstr-string-string","text":"Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt;","title":"escapeHTML(str: string): string"},{"location":"javascript/new-api_core/#escaperegexpstr-string-string","text":"Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions.","title":"escapeRegExp(str: string): string"},{"location":"javascript/new-api_core/#lcfirststr-string-string","text":"Makes a string's first character lowercase.","title":"lcfirst(str: string): string"},{"location":"javascript/new-api_core/#ucfirststr-string-string","text":"Makes a string's first character uppercase.","title":"ucfirst(str: string): string"},{"location":"javascript/new-api_core/#unescapehtmlstr-string-string","text":"Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"unescapeHTML(str: string): string"},{"location":"javascript/new-api_data-structures/","text":"Data Structures - JavaScript API # Introduction # JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11. Dictionary # Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object. set(key: string, value: any) # Adds or updates an item using the provided key. Numeric keys will be converted into strings. delete(key: string) # Removes an item from the collection. has(key: string): boolean # Returns true if the key is contained in the collection. get(key: string): any # Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence. forEach(callback: (value: any, key: string) => void) # Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key. size: number # This read-only property counts the number of items in the collection. List # Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array. add(value: any) # Adds a value to the list. If the value is already part of the list, this method will silently abort. clear() # Resets the collection. delete(value: any): boolean # Attempts to remove a value from the list, it returns true if the value has been part of the list. forEach(callback: (value: any) => void) # Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value. has(value: any): boolean # Returns true if the provided value is part of this list. size: number # This read-only property counts the number of items in the list. ObjectMap # This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects. set(key: Object, value: Object) # Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten. delete(key: Object) # Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection. has(key: Object): boolean # Returns true if there is a value for the provided key in this collection. get(key: Object): Object | undefined # Retrieves the value of the provided key, or undefined if the key was not found.","title":"Data Structures"},{"location":"javascript/new-api_data-structures/#data-structures-javascript-api","text":"","title":"Data Structures - JavaScript API"},{"location":"javascript/new-api_data-structures/#introduction","text":"JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11.","title":"Introduction"},{"location":"javascript/new-api_data-structures/#dictionary","text":"Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object.","title":"Dictionary"},{"location":"javascript/new-api_data-structures/#setkey-string-value-any","text":"Adds or updates an item using the provided key. Numeric keys will be converted into strings.","title":"set(key: string, value: any)"},{"location":"javascript/new-api_data-structures/#deletekey-string","text":"Removes an item from the collection.","title":"delete(key: string)"},{"location":"javascript/new-api_data-structures/#haskey-string-boolean","text":"Returns true if the key is contained in the collection.","title":"has(key: string): boolean"},{"location":"javascript/new-api_data-structures/#getkey-string-any","text":"Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence.","title":"get(key: string): any"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-key-string-void","text":"Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key.","title":"forEach(callback: (value: any, key: string) =&gt; void)"},{"location":"javascript/new-api_data-structures/#size-number","text":"This read-only property counts the number of items in the collection.","title":"size: number"},{"location":"javascript/new-api_data-structures/#list","text":"Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array.","title":"List"},{"location":"javascript/new-api_data-structures/#addvalue-any","text":"Adds a value to the list. If the value is already part of the list, this method will silently abort.","title":"add(value: any)"},{"location":"javascript/new-api_data-structures/#clear","text":"Resets the collection.","title":"clear()"},{"location":"javascript/new-api_data-structures/#deletevalue-any-boolean","text":"Attempts to remove a value from the list, it returns true if the value has been part of the list.","title":"delete(value: any): boolean"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-void","text":"Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value.","title":"forEach(callback: (value: any) =&gt; void)"},{"location":"javascript/new-api_data-structures/#hasvalue-any-boolean","text":"Returns true if the provided value is part of this list.","title":"has(value: any): boolean"},{"location":"javascript/new-api_data-structures/#size-number_1","text":"This read-only property counts the number of items in the list.","title":"size: number"},{"location":"javascript/new-api_data-structures/#objectmap","text":"This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects.","title":"ObjectMap"},{"location":"javascript/new-api_data-structures/#setkey-object-value-object","text":"Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten.","title":"set(key: Object, value: Object)"},{"location":"javascript/new-api_data-structures/#deletekey-object","text":"Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection.","title":"delete(key: Object)"},{"location":"javascript/new-api_data-structures/#haskey-object-boolean","text":"Returns true if there is a value for the provided key in this collection.","title":"has(key: Object): boolean"},{"location":"javascript/new-api_data-structures/#getkey-object-object-undefined","text":"Retrieves the value of the provided key, or undefined if the key was not found.","title":"get(key: Object): Object | undefined"},{"location":"javascript/new-api_dialogs/","text":"Dialogs - JavaScript API # Introduction # Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs. _dialogSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; }); id: string # The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id. source: any # There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog. options: Object # All configuration options and callbacks are handled through this object. options.backdropCloseOnClick: boolean # Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false . options.closable: boolean # Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only. options.closeButtonLabel: string # Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button. options.closeConfirmMessage: string # Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user. options.title: string # Defaults to \"\" . The phrase that is displayed in the dialog title. options.onBeforeClose: (id: string) => void # Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented. options.onClose: (id: string) => void # Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point. options.onShow: (content: Element) => void # Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet. setTitle(id: string | Object, title: string) # Sets the title of a dialog. setCallback(id: string | Object, key: string, value: (data: any) => void | null) # Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow . rebuild(id: string | Object) # Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback. close(id: string | Object) # Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation. getDialog(id: string | Object): Object # This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element. isOpen(id: string | Object): boolean # Returns true if the dialog exists and is open.","title":"Dialogs"},{"location":"javascript/new-api_dialogs/#dialogs-javascript-api","text":"","title":"Dialogs - JavaScript API"},{"location":"javascript/new-api_dialogs/#introduction","text":"Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs.","title":"Introduction"},{"location":"javascript/new-api_dialogs/#_dialogsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; });","title":"_dialogSetup()"},{"location":"javascript/new-api_dialogs/#id-string","text":"The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id.","title":"id: string"},{"location":"javascript/new-api_dialogs/#source-any","text":"There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog.","title":"source: any"},{"location":"javascript/new-api_dialogs/#options-object","text":"All configuration options and callbacks are handled through this object.","title":"options: Object"},{"location":"javascript/new-api_dialogs/#optionsbackdropcloseonclick-boolean","text":"Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false .","title":"options.backdropCloseOnClick: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosable-boolean","text":"Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only.","title":"options.closable: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosebuttonlabel-string","text":"Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button.","title":"options.closeButtonLabel: string"},{"location":"javascript/new-api_dialogs/#optionscloseconfirmmessage-string","text":"Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user.","title":"options.closeConfirmMessage: string"},{"location":"javascript/new-api_dialogs/#optionstitle-string","text":"Defaults to \"\" . The phrase that is displayed in the dialog title.","title":"options.title: string"},{"location":"javascript/new-api_dialogs/#optionsonbeforeclose-id-string-void","text":"Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented.","title":"options.onBeforeClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonclose-id-string-void","text":"Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point.","title":"options.onClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonshow-content-element-void","text":"Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet.","title":"options.onShow: (content: Element) =&gt; void"},{"location":"javascript/new-api_dialogs/#settitleid-string-object-title-string","text":"Sets the title of a dialog.","title":"setTitle(id: string | Object, title: string)"},{"location":"javascript/new-api_dialogs/#setcallbackid-string-object-key-string-value-data-any-void-null","text":"Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow .","title":"setCallback(id: string | Object, key: string, value: (data: any) =&gt; void | null)"},{"location":"javascript/new-api_dialogs/#rebuildid-string-object","text":"Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback.","title":"rebuild(id: string | Object)"},{"location":"javascript/new-api_dialogs/#closeid-string-object","text":"Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation.","title":"close(id: string | Object)"},{"location":"javascript/new-api_dialogs/#getdialogid-string-object-object","text":"This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element.","title":"getDialog(id: string | Object): Object"},{"location":"javascript/new-api_dialogs/#isopenid-string-object-boolean","text":"Returns true if the dialog exists and is open.","title":"isOpen(id: string | Object): boolean"},{"location":"javascript/new-api_dom/","text":"Working with the DOM - JavaScript API # Helper Functions # There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports. Dom/Util # createFragmentFromHtml(html: string): DocumentFragment # Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes. identify(element: Element): string # Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead. outerHeight(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom . outerWidth(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right . outerDimensions(element: Element): { height: number, width: number } # Computes the outer dimensions of an element including its margins. offset(element: Element): { top: number, left: number } # Computes the element's offset relative to the top left corner of the document. setInnerHtml(element: Element, innerHtml: string) # Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance. contains(element: Element, child: Element): boolean # Evaluates if element is a direct or indirect parent element of child . unwrapChildNodes(element: Element) # Moves all child nodes out of element while maintaining their order, then removes element from the document. Dom/ChangeListener # This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"DOM"},{"location":"javascript/new-api_dom/#working-with-the-dom-javascript-api","text":"","title":"Working with the DOM - JavaScript API"},{"location":"javascript/new-api_dom/#helper-functions","text":"There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports.","title":"Helper Functions"},{"location":"javascript/new-api_dom/#domutil","text":"","title":"Dom/Util"},{"location":"javascript/new-api_dom/#createfragmentfromhtmlhtml-string-documentfragment","text":"Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes.","title":"createFragmentFromHtml(html: string): DocumentFragment"},{"location":"javascript/new-api_dom/#identifyelement-element-string","text":"Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead.","title":"identify(element: Element): string"},{"location":"javascript/new-api_dom/#outerheightelement-element-styles-cssstyledeclaration-number","text":"Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom .","title":"outerHeight(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerwidthelement-element-styles-cssstyledeclaration-number","text":"Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right .","title":"outerWidth(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerdimensionselement-element-height-number-width-number","text":"Computes the outer dimensions of an element including its margins.","title":"outerDimensions(element: Element): { height: number, width: number }"},{"location":"javascript/new-api_dom/#offsetelement-element-top-number-left-number","text":"Computes the element's offset relative to the top left corner of the document.","title":"offset(element: Element): { top: number, left: number }"},{"location":"javascript/new-api_dom/#setinnerhtmlelement-element-innerhtml-string","text":"Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance.","title":"setInnerHtml(element: Element, innerHtml: string)"},{"location":"javascript/new-api_dom/#containselement-element-child-element-boolean","text":"Evaluates if element is a direct or indirect parent element of child .","title":"contains(element: Element, child: Element): boolean"},{"location":"javascript/new-api_dom/#unwrapchildnodeselement-element","text":"Moves all child nodes out of element while maintaining their order, then removes element from the document.","title":"unwrapChildNodes(element: Element)"},{"location":"javascript/new-api_dom/#domchangelistener","text":"This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"Dom/ChangeListener"},{"location":"javascript/new-api_events/","text":"Event Handling - JavaScript API # EventKey # This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); }); ArrowDown(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2193 key. ArrowLeft(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2190 key. ArrowRight(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2192 key. ArrowUp(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2191 key. Comma(event: KeyboardEvent): boolean # Returns true if the user has pressed the , key. Enter(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b2 key. Escape(event: KeyboardEvent): boolean # Returns true if the user has pressed the Esc key. Tab(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b9 key. EventHandler # A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time. Identifiying Events with the Developer Tools # The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo add(identifier: string, action: string, callback: (data: Object) => void): string # Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method. fire(identifier: string, action: string, data?: Object) # Triggers an event using an optional data object that is passed to each listener by reference. remove(identifier: string, action: string, uuid: string) # Removes a previously registered event listener using the UUID returned by add() . removeAll(identifier: string, action: string) # Removes all event listeners registered for the provided identifier and action . removeAllBySuffix(identifier: string, suffix: string) # Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"Event Handling"},{"location":"javascript/new-api_events/#event-handling-javascript-api","text":"","title":"Event Handling - JavaScript API"},{"location":"javascript/new-api_events/#eventkey","text":"This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); });","title":"EventKey"},{"location":"javascript/new-api_events/#arrowdownevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2193 key.","title":"ArrowDown(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowleftevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2190 key.","title":"ArrowLeft(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowrightevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2192 key.","title":"ArrowRight(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowupevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2191 key.","title":"ArrowUp(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#commaevent-keyboardevent-boolean","text":"Returns true if the user has pressed the , key.","title":"Comma(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#enterevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b2 key.","title":"Enter(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#escapeevent-keyboardevent-boolean","text":"Returns true if the user has pressed the Esc key.","title":"Escape(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#tabevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b9 key.","title":"Tab(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#eventhandler","text":"A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time.","title":"EventHandler"},{"location":"javascript/new-api_events/#identifiying-events-with-the-developer-tools","text":"The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo","title":"Identifiying Events with the Developer Tools"},{"location":"javascript/new-api_events/#addidentifier-string-action-string-callback-data-object-void-string","text":"Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method.","title":"add(identifier: string, action: string, callback: (data: Object) =&gt; void): string"},{"location":"javascript/new-api_events/#fireidentifier-string-action-string-data-object","text":"Triggers an event using an optional data object that is passed to each listener by reference.","title":"fire(identifier: string, action: string, data?: Object)"},{"location":"javascript/new-api_events/#removeidentifier-string-action-string-uuid-string","text":"Removes a previously registered event listener using the UUID returned by add() .","title":"remove(identifier: string, action: string, uuid: string)"},{"location":"javascript/new-api_events/#removeallidentifier-string-action-string","text":"Removes all event listeners registered for the provided identifier and action .","title":"removeAll(identifier: string, action: string)"},{"location":"javascript/new-api_events/#removeallbysuffixidentifier-string-suffix-string","text":"Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"removeAllBySuffix(identifier: string, suffix: string)"},{"location":"javascript/new-api_ui/","text":"User Interface - JavaScript API # Ui/Alignment # Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements. set(element: Element, referenceElement: Element, options: Object) # Calculates and sets the alignment of the element element . verticalOffset: number # Defaults to 0 . Creates a gap between the element and the reference element, in pixels. pointer: boolean # Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer . pointerOffset: number # Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center. pointerClassNames: string[] # Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element. refDimensionsElement: Element # Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions. horizontal: string # This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used. vertical: string # Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used. allowFlip: string # The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints. Ui/CloseOverlay # Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); }); add(identifier: string, callback: () => void) # Adds a callback that will be invoked when the user clicks anywhere else. Ui/Confirmation # Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); }); show(options: Object) # Displays a dialog overlay with actions buttons to confirm or reject the dialog. cancel: (parameters: Object) => void # Defaults to null . Callback that is invoked when the dialog was rejected. confirm: (parameters: Object) => void # Defaults to null . Callback that is invoked when the user has confirmed the dialog. message: string # Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled. messageIsHtml # Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML. parameters: Object # Optional list of parameter options that will be passed to the cancel() and confirm() callbacks. template: string # An optional HTML template that will be inserted into the dialog content area, but after the message section. Ui/Notification # Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); }); show(message: string, callback?: () => void, cssClassName?: string) # Shows the notification and executes the callback after 2 seconds.","title":"User Interface"},{"location":"javascript/new-api_ui/#user-interface-javascript-api","text":"","title":"User Interface - JavaScript API"},{"location":"javascript/new-api_ui/#uialignment","text":"Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements.","title":"Ui/Alignment"},{"location":"javascript/new-api_ui/#setelement-element-referenceelement-element-options-object","text":"Calculates and sets the alignment of the element element .","title":"set(element: Element, referenceElement: Element, options: Object)"},{"location":"javascript/new-api_ui/#verticaloffset-number","text":"Defaults to 0 . Creates a gap between the element and the reference element, in pixels.","title":"verticalOffset: number"},{"location":"javascript/new-api_ui/#pointer-boolean","text":"Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer .","title":"pointer: boolean"},{"location":"javascript/new-api_ui/#pointeroffset-number","text":"Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center.","title":"pointerOffset: number"},{"location":"javascript/new-api_ui/#pointerclassnames-string","text":"Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element.","title":"pointerClassNames: string[]"},{"location":"javascript/new-api_ui/#refdimensionselement-element","text":"Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions.","title":"refDimensionsElement: Element"},{"location":"javascript/new-api_ui/#horizontal-string","text":"This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used.","title":"horizontal: string"},{"location":"javascript/new-api_ui/#vertical-string","text":"Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used.","title":"vertical: string"},{"location":"javascript/new-api_ui/#allowflip-string","text":"The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints.","title":"allowFlip: string"},{"location":"javascript/new-api_ui/#uicloseoverlay","text":"Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); });","title":"Ui/CloseOverlay"},{"location":"javascript/new-api_ui/#addidentifier-string-callback-void","text":"Adds a callback that will be invoked when the user clicks anywhere else.","title":"add(identifier: string, callback: () =&gt; void)"},{"location":"javascript/new-api_ui/#uiconfirmation","text":"Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); });","title":"Ui/Confirmation"},{"location":"javascript/new-api_ui/#showoptions-object","text":"Displays a dialog overlay with actions buttons to confirm or reject the dialog.","title":"show(options: Object)"},{"location":"javascript/new-api_ui/#cancel-parameters-object-void","text":"Defaults to null . Callback that is invoked when the dialog was rejected.","title":"cancel: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#confirm-parameters-object-void","text":"Defaults to null . Callback that is invoked when the user has confirmed the dialog.","title":"confirm: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#message-string","text":"Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled.","title":"message: string"},{"location":"javascript/new-api_ui/#messageishtml","text":"Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML.","title":"messageIsHtml"},{"location":"javascript/new-api_ui/#parameters-object","text":"Optional list of parameter options that will be passed to the cancel() and confirm() callbacks.","title":"parameters: Object"},{"location":"javascript/new-api_ui/#template-string","text":"An optional HTML template that will be inserted into the dialog content area, but after the message section.","title":"template: string"},{"location":"javascript/new-api_ui/#uinotification","text":"Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); });","title":"Ui/Notification"},{"location":"javascript/new-api_ui/#showmessage-string-callback-void-cssclassname-string","text":"Shows the notification and executes the callback after 2 seconds.","title":"show(message: string, callback?: () =&gt; void, cssClassName?: string)"},{"location":"javascript/new-api_writing-a-module/","text":"Writing a Module - JavaScript API # Introduction # The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start. Defining a Module # The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; }); Loading a Module # Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script > Module Aliases # Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Writing a module"},{"location":"javascript/new-api_writing-a-module/#writing-a-module-javascript-api","text":"","title":"Writing a Module - JavaScript API"},{"location":"javascript/new-api_writing-a-module/#introduction","text":"The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start.","title":"Introduction"},{"location":"javascript/new-api_writing-a-module/#defining-a-module","text":"The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; });","title":"Defining a Module"},{"location":"javascript/new-api_writing-a-module/#loading-a-module","text":"Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script >","title":"Loading a Module"},{"location":"javascript/new-api_writing-a-module/#module-aliases","text":"Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Module Aliases"},{"location":"migration/wcf21/css/","text":"WCF 2.1.x - CSS # The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"CSS"},{"location":"migration/wcf21/css/#wcf-21x-css","text":"The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"WCF 2.1.x - CSS"},{"location":"migration/wcf21/package/","text":"WCF 2.1.x - Package Components # package.xml # Short Instructions # Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability. Example # <instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions> Exceptions # These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar acpMenu.xml # Renamed Categories # The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application Submenu Items # Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item. Example # <!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem> Common Icon Names # You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload box.xml # The box PIP has been added. cronjob.xml # Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete. eventListener.xml # Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete. menu.xml # The menu PIP has been added. menuItem.xml # The menuItem PIP has been added. objectType.xml # The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported. option.xml # The module.display category has been renamed into module.customization . page.xml # The page PIP has been added. pageMenu.xml # The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"Package Components"},{"location":"migration/wcf21/package/#wcf-21x-package-components","text":"","title":"WCF 2.1.x - Package Components"},{"location":"migration/wcf21/package/#packagexml","text":"","title":"package.xml"},{"location":"migration/wcf21/package/#short-instructions","text":"Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability.","title":"Short Instructions"},{"location":"migration/wcf21/package/#example","text":"<instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions>","title":"Example"},{"location":"migration/wcf21/package/#exceptions","text":"These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar","title":"Exceptions"},{"location":"migration/wcf21/package/#acpmenuxml","text":"","title":"acpMenu.xml"},{"location":"migration/wcf21/package/#renamed-categories","text":"The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application","title":"Renamed Categories"},{"location":"migration/wcf21/package/#submenu-items","text":"Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item.","title":"Submenu Items"},{"location":"migration/wcf21/package/#example_1","text":"<!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem>","title":"Example"},{"location":"migration/wcf21/package/#common-icon-names","text":"You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload","title":"Common Icon Names"},{"location":"migration/wcf21/package/#boxxml","text":"The box PIP has been added.","title":"box.xml"},{"location":"migration/wcf21/package/#cronjobxml","text":"Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete.","title":"cronjob.xml"},{"location":"migration/wcf21/package/#eventlistenerxml","text":"Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete.","title":"eventListener.xml"},{"location":"migration/wcf21/package/#menuxml","text":"The menu PIP has been added.","title":"menu.xml"},{"location":"migration/wcf21/package/#menuitemxml","text":"The menuItem PIP has been added.","title":"menuItem.xml"},{"location":"migration/wcf21/package/#objecttypexml","text":"The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported.","title":"objectType.xml"},{"location":"migration/wcf21/package/#optionxml","text":"The module.display category has been renamed into module.customization .","title":"option.xml"},{"location":"migration/wcf21/package/#pagexml","text":"The page PIP has been added.","title":"page.xml"},{"location":"migration/wcf21/package/#pagemenuxml","text":"The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"pageMenu.xml"},{"location":"migration/wcf21/php/","text":"WCF 2.1.x - PHP # Message Processing # WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done. Input Processing for Storage # The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message. Embedded Objects # Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); } Rendering the Message # The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml (); Simplified Output # At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 ); Plaintext Output # The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 ); Rebuilding Data # Converting from BBCode # Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml (); Extracting Embedded Objects # The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here Breadcrumbs / Page Location # Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object ); Pages and Forms # The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work. Search # ISearchableObjectType # Added the setLocation() method that is used to set the current page location based on the search result. SearchIndexManager # The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"PHP API"},{"location":"migration/wcf21/php/#wcf-21x-php","text":"","title":"WCF 2.1.x - PHP"},{"location":"migration/wcf21/php/#message-processing","text":"WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done.","title":"Message Processing"},{"location":"migration/wcf21/php/#input-processing-for-storage","text":"The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message.","title":"Input Processing for Storage"},{"location":"migration/wcf21/php/#embedded-objects","text":"Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); }","title":"Embedded Objects"},{"location":"migration/wcf21/php/#rendering-the-message","text":"The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml ();","title":"Rendering the Message"},{"location":"migration/wcf21/php/#simplified-output","text":"At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 );","title":"Simplified Output"},{"location":"migration/wcf21/php/#plaintext-output","text":"The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 );","title":"Plaintext Output"},{"location":"migration/wcf21/php/#rebuilding-data","text":"","title":"Rebuilding Data"},{"location":"migration/wcf21/php/#converting-from-bbcode","text":"Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml ();","title":"Converting from BBCode"},{"location":"migration/wcf21/php/#extracting-embedded-objects","text":"The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here","title":"Extracting Embedded Objects"},{"location":"migration/wcf21/php/#breadcrumbs-page-location","text":"Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object );","title":"Breadcrumbs / Page Location"},{"location":"migration/wcf21/php/#pages-and-forms","text":"The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work.","title":"Pages and Forms"},{"location":"migration/wcf21/php/#search","text":"","title":"Search"},{"location":"migration/wcf21/php/#isearchableobjecttype","text":"Added the setLocation() method that is used to set the current page location based on the search result.","title":"ISearchableObjectType"},{"location":"migration/wcf21/php/#searchindexmanager","text":"The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"SearchIndexManager"},{"location":"migration/wcf21/templates/","text":"WCF 2.1.x - Templates # Page Layout # The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information. Sidebars # Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section > Forms # The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing. Icons # The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" > Changed Icon Names # Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4. Changed Classes # .dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList Removed Elements and Classes # <nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible. Simple Example # The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' } Full Example # { * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' } Content Header # There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible. Recommended Approach # { * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture } Alternative # { capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Templates"},{"location":"migration/wcf21/templates/#wcf-21x-templates","text":"","title":"WCF 2.1.x - Templates"},{"location":"migration/wcf21/templates/#page-layout","text":"The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information.","title":"Page Layout"},{"location":"migration/wcf21/templates/#sidebars","text":"Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section >","title":"Sidebars"},{"location":"migration/wcf21/templates/#forms","text":"The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing.","title":"Forms"},{"location":"migration/wcf21/templates/#icons","text":"The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" >","title":"Icons"},{"location":"migration/wcf21/templates/#changed-icon-names","text":"Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4.","title":"Changed Icon Names"},{"location":"migration/wcf21/templates/#changed-classes","text":".dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList","title":"Changed Classes"},{"location":"migration/wcf21/templates/#removed-elements-and-classes","text":"<nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible.","title":"Removed Elements and Classes"},{"location":"migration/wcf21/templates/#simple-example","text":"The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' }","title":"Simple Example"},{"location":"migration/wcf21/templates/#full-example","text":"{ * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' }","title":"Full Example"},{"location":"migration/wcf21/templates/#content-header","text":"There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible.","title":"Content Header"},{"location":"migration/wcf21/templates/#recommended-approach","text":"{ * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture }","title":"Recommended Approach"},{"location":"migration/wcf21/templates/#alternative","text":"{ capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Alternative"},{"location":"migration/wsc30/css/","text":"Migrating from WSC 3.0 - CSS # New Style Variables # The new style variables are only applied to styles that have the compatibility set to WSC 3.1 wcfContentContainer # The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color wcfEditorButton # These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons Color Variables in alert.scss # The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"CSS"},{"location":"migration/wsc30/css/#migrating-from-wsc-30-css","text":"","title":"Migrating from WSC 3.0 - CSS"},{"location":"migration/wsc30/css/#new-style-variables","text":"The new style variables are only applied to styles that have the compatibility set to WSC 3.1","title":"New Style Variables"},{"location":"migration/wsc30/css/#wcfcontentcontainer","text":"The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color","title":"wcfContentContainer"},{"location":"migration/wsc30/css/#wcfeditorbutton","text":"These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons","title":"wcfEditorButton"},{"location":"migration/wsc30/css/#color-variables-in-alertscss","text":"The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"Color Variables in alert.scss"},{"location":"migration/wsc30/javascript/","text":"Migrating from WSC 3.0 - JavaScript # Accelerated Guest View / Tiny Builds # The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever. Code Templates for Tiny Builds # The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now. Legacy JavaScript # if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); } require.js Modules # define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; }); Including tinified builds through {js} # The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state. Real Error Messages for AJAX Responses # The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms. Example Code # define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; }); Simplified Form Submit in Dialogs # Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field. Helper Function for Inline Error Messages # Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef) Example Code # require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"JavaScript API"},{"location":"migration/wsc30/javascript/#migrating-from-wsc-30-javascript","text":"","title":"Migrating from WSC 3.0 - JavaScript"},{"location":"migration/wsc30/javascript/#accelerated-guest-view-tiny-builds","text":"The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever.","title":"Accelerated Guest View / Tiny Builds"},{"location":"migration/wsc30/javascript/#code-templates-for-tiny-builds","text":"The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now.","title":"Code Templates for Tiny Builds"},{"location":"migration/wsc30/javascript/#legacy-javascript","text":"if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); }","title":"Legacy JavaScript"},{"location":"migration/wsc30/javascript/#requirejs-modules","text":"define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; });","title":"require.js Modules"},{"location":"migration/wsc30/javascript/#including-tinified-builds-through-js","text":"The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state.","title":"Including tinified builds through {js}"},{"location":"migration/wsc30/javascript/#real-error-messages-for-ajax-responses","text":"The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms.","title":"Real Error Messages for AJAX Responses"},{"location":"migration/wsc30/javascript/#example-code","text":"define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; });","title":"Example Code"},{"location":"migration/wsc30/javascript/#simplified-form-submit-in-dialogs","text":"Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field.","title":"Simplified Form Submit in Dialogs"},{"location":"migration/wsc30/javascript/#helper-function-for-inline-error-messages","text":"Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef)","title":"Helper Function for Inline Error Messages"},{"location":"migration/wsc30/javascript/#example-code_1","text":"require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"Example Code"},{"location":"migration/wsc30/package/","text":"Migrating from WSC 3.0 - Package Components # Cronjob Scheduler uses Server Timezone # The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel. Exclude Pages from becoming a Landing Page # Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml . Example Code # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data> New Package Installation Plugin for Media Providers # Please refer to the documentation of the mediaProvider.xml to learn more. Limited Forward-Compatibility for Plugins # Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Package Components"},{"location":"migration/wsc30/package/#migrating-from-wsc-30-package-components","text":"","title":"Migrating from WSC 3.0 - Package Components"},{"location":"migration/wsc30/package/#cronjob-scheduler-uses-server-timezone","text":"The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel.","title":"Cronjob Scheduler uses Server Timezone"},{"location":"migration/wsc30/package/#exclude-pages-from-becoming-a-landing-page","text":"Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml .","title":"Exclude Pages from becoming a Landing Page"},{"location":"migration/wsc30/package/#example-code","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data>","title":"Example Code"},{"location":"migration/wsc30/package/#new-package-installation-plugin-for-media-providers","text":"Please refer to the documentation of the mediaProvider.xml to learn more.","title":"New Package Installation Plugin for Media Providers"},{"location":"migration/wsc30/package/#limited-forward-compatibility-for-plugins","text":"Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Limited Forward-Compatibility for Plugins"},{"location":"migration/wsc30/php/","text":"Migrating from WSC 3.0 - PHP # Approval-System for Comments # Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; } Raw HTML in User Activity Events # User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; } Permission to View Likes of an Object # Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } } Developer Tools: Sync Feature # The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } } Media Providers # Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation. Example Implementation # mediaProvider.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data> PHP Callback # The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } } Re-Evaluate HTML Messages # You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously. Example Usage # <? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"PHP API"},{"location":"migration/wsc30/php/#migrating-from-wsc-30-php","text":"","title":"Migrating from WSC 3.0 - PHP"},{"location":"migration/wsc30/php/#approval-system-for-comments","text":"Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; }","title":"Approval-System for Comments"},{"location":"migration/wsc30/php/#raw-html-in-user-activity-events","text":"User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; }","title":"Raw HTML in User Activity Events"},{"location":"migration/wsc30/php/#permission-to-view-likes-of-an-object","text":"Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } }","title":"Permission to View Likes of an Object"},{"location":"migration/wsc30/php/#developer-tools-sync-feature","text":"The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } }","title":"Developer Tools: Sync Feature"},{"location":"migration/wsc30/php/#media-providers","text":"Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation.","title":"Media Providers"},{"location":"migration/wsc30/php/#example-implementation","text":"","title":"Example Implementation"},{"location":"migration/wsc30/php/#mediaproviderxml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data>","title":"mediaProvider.xml"},{"location":"migration/wsc30/php/#php-callback","text":"The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } }","title":"PHP Callback"},{"location":"migration/wsc30/php/#re-evaluate-html-messages","text":"You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously.","title":"Re-Evaluate HTML Messages"},{"location":"migration/wsc30/php/#example-usage","text":"<? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"Example Usage"},{"location":"migration/wsc30/templates/","text":"Migrating from WSC 3.0 - Templates # Comment-System Overhaul # Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments. Adding Comments # Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul > Redesigned ACP User List # Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li > Sidebar Toogle-Buttons on Mobile Device # You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Templates"},{"location":"migration/wsc30/templates/#migrating-from-wsc-30-templates","text":"","title":"Migrating from WSC 3.0 - Templates"},{"location":"migration/wsc30/templates/#comment-system-overhaul","text":"Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments.","title":"Comment-System Overhaul"},{"location":"migration/wsc30/templates/#adding-comments","text":"Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul >","title":"Adding Comments"},{"location":"migration/wsc30/templates/#redesigned-acp-user-list","text":"Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li >","title":"Redesigned ACP User List"},{"location":"migration/wsc30/templates/#sidebar-toogle-buttons-on-mobile-device","text":"You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Sidebar Toogle-Buttons on Mobile Device"},{"location":"migration/wsc31/form-builder/","text":"Migrating from WSC 3.1 - Form Builder # Example: Two Text Form Fields # As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#migrating-from-wsc-31-form-builder","text":"","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#example-two-text-form-fields","text":"As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Example: Two Text Form Fields"},{"location":"migration/wsc31/like/","text":"Migrating from WSC 3.1 - Like System # Introduction # With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments. Limitations if no adjustments are made to the existing code # If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes Migration # Notifications # Mark notification as compatible # Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again. Language Variables # Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # {prefix}.like.title Reaction to a {objectName} {prefix}.like.title.stacked {#$count} users reacted to your {objectName} {prefix}.like.message {@$author->getAnchorTag()} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} others{/if} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{objectTypeName}.like.notification.like Notify me when someone reacted to my {objectName} German # {prefix}.like.title Reaktion auf einen {objectName} {prefix}.like.title.stacked {#$count} Benutzern haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert {prefix}.like.message {@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{object_type_name}.like.notification.like Jemandem hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert Recent Activity # To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}. German # wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert. Comments # If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type> Forward Compatibility # So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#migrating-from-wsc-31-like-system","text":"","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#introduction","text":"With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments.","title":"Introduction"},{"location":"migration/wsc31/like/#limitations-if-no-adjustments-are-made-to-the-existing-code","text":"If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes","title":"Limitations if no adjustments are made to the existing code"},{"location":"migration/wsc31/like/#migration","text":"","title":"Migration"},{"location":"migration/wsc31/like/#notifications","text":"","title":"Notifications"},{"location":"migration/wsc31/like/#mark-notification-as-compatible","text":"Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again.","title":"Mark notification as compatible"},{"location":"migration/wsc31/like/#language-variables","text":"Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Language Variables"},{"location":"migration/wsc31/like/#recent-activity","text":"To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Recent Activity"},{"location":"migration/wsc31/like/#english_1","text":"wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}.","title":"English"},{"location":"migration/wsc31/like/#german_1","text":"wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert.","title":"German"},{"location":"migration/wsc31/like/#comments","text":"If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type>","title":"Comments"},{"location":"migration/wsc31/like/#forward-compatibility","text":"So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Forward Compatibility"},{"location":"migration/wsc31/php/","text":"Migrating from WSC 3.1 - PHP # Form Builder # WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway. Like System # WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here . User Content Providers # User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects. PHP Class # First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } } Object Type # Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type: nicevalue # Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first. hidden # Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically! requiredobjecttype # Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important. PHP Database API # WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP API"},{"location":"migration/wsc31/php/#migrating-from-wsc-31-php","text":"","title":"Migrating from WSC 3.1 - PHP"},{"location":"migration/wsc31/php/#form-builder","text":"WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway.","title":"Form Builder"},{"location":"migration/wsc31/php/#like-system","text":"WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here .","title":"Like System"},{"location":"migration/wsc31/php/#user-content-providers","text":"User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects.","title":"User Content Providers"},{"location":"migration/wsc31/php/#php-class","text":"First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } }","title":"PHP Class"},{"location":"migration/wsc31/php/#object-type","text":"Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type:","title":"Object Type"},{"location":"migration/wsc31/php/#nicevalue","text":"Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first.","title":"nicevalue"},{"location":"migration/wsc31/php/#hidden","text":"Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically!","title":"hidden"},{"location":"migration/wsc31/php/#requiredobjecttype","text":"Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important.","title":"requiredobjecttype"},{"location":"migration/wsc31/php/#php-database-api","text":"WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP Database API"},{"location":"migration/wsc52/libraries/","text":"Migrating from WSC 5.2 - Third Party Libraries # SCSS Compiler # WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 . Guzzle # WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Third Party Libraries"},{"location":"migration/wsc52/libraries/#migrating-from-wsc-52-third-party-libraries","text":"","title":"Migrating from WSC 5.2 - Third Party Libraries"},{"location":"migration/wsc52/libraries/#scss-compiler","text":"WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 .","title":"SCSS Compiler"},{"location":"migration/wsc52/libraries/#guzzle","text":"WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Guzzle"},{"location":"migration/wsc52/php/","text":"Migrating from WSC 5.2 - PHP # Comments # The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager . Event Listeners # The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener . Email Activation # Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() . *AddForm # WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]); User Generated Links # It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available. Resource Management When Scaling Images # It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details. Toggle for Accelerated Mobile Pages (AMP) # Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"PHP API"},{"location":"migration/wsc52/php/#migrating-from-wsc-52-php","text":"","title":"Migrating from WSC 5.2 - PHP"},{"location":"migration/wsc52/php/#comments","text":"The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager .","title":"Comments"},{"location":"migration/wsc52/php/#event-listeners","text":"The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener .","title":"Event Listeners"},{"location":"migration/wsc52/php/#email-activation","text":"Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() .","title":"Email Activation"},{"location":"migration/wsc52/php/#addform","text":"WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]);","title":"*AddForm"},{"location":"migration/wsc52/php/#user-generated-links","text":"It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available.","title":"User Generated Links"},{"location":"migration/wsc52/php/#resource-management-when-scaling-images","text":"It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details.","title":"Resource Management When Scaling Images"},{"location":"migration/wsc52/php/#toggle-for-accelerated-mobile-pages-amp","text":"Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"Toggle for Accelerated Mobile Pages (AMP)"},{"location":"migration/wsc52/templates/","text":"Migrating from WSC 5.2 - Templates and Languages # {jslang} # Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script> Template Plugins # The {anchor} , {plural} , and {user} template plugins have been added. Notification Language Items # In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language } Popovers # Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Templates and Languages"},{"location":"migration/wsc52/templates/#migrating-from-wsc-52-templates-and-languages","text":"","title":"Migrating from WSC 5.2 - Templates and Languages"},{"location":"migration/wsc52/templates/#jslang","text":"Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script>","title":"{jslang}"},{"location":"migration/wsc52/templates/#template-plugins","text":"The {anchor} , {plural} , and {user} template plugins have been added.","title":"Template Plugins"},{"location":"migration/wsc52/templates/#notification-language-items","text":"In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language }","title":"Notification Language Items"},{"location":"migration/wsc52/templates/#popovers","text":"Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Popovers"},{"location":"migration/wsc53/javascript/","text":"Migrating from WSC 5.3 - JavaScript # WCF_CLICK_EVENT # For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev )); WCF.Action.Delete and WCF.Action.Toggle # WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" {* *}data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> {* \u2026 *} </tr> </thead> <tbody> {foreach from=$objects item=foo} <tr class=\"jsObjectActionObject\" data-object-id=\"{@$foo->getObjectID()}\"> <td class=\"columnIcon\"> {objectAction action=\"toggle\" isDisabled=$foo->isDisabled} {objectAction action=\"delete\" objectTitle=$foo->getTitle()} {* \u2026 *} </td> {* \u2026 *} </tr> {/foreach} </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 . WCF.Table.EmptyTableHandler # When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"JavaScript"},{"location":"migration/wsc53/javascript/#migrating-from-wsc-53-javascript","text":"","title":"Migrating from WSC 5.3 - JavaScript"},{"location":"migration/wsc53/javascript/#wcf_click_event","text":"For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev ));","title":"WCF_CLICK_EVENT"},{"location":"migration/wsc53/javascript/#wcfactiondelete-and-wcfactiontoggle","text":"WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" {* *}data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> {* \u2026 *} </tr> </thead> <tbody> {foreach from=$objects item=foo} <tr class=\"jsObjectActionObject\" data-object-id=\"{@$foo->getObjectID()}\"> <td class=\"columnIcon\"> {objectAction action=\"toggle\" isDisabled=$foo->isDisabled} {objectAction action=\"delete\" objectTitle=$foo->getTitle()} {* \u2026 *} </td> {* \u2026 *} </tr> {/foreach} </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 .","title":"WCF.Action.Delete and WCF.Action.Toggle"},{"location":"migration/wsc53/javascript/#wcftableemptytablehandler","text":"When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"WCF.Table.EmptyTableHandler"},{"location":"migration/wsc53/libraries/","text":"Migrating from WSC 5.3 - Third Party Libraries # Guzzle # The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7. Emogrifier / CSS Inliner # The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases. scssphp # scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details. Constant Time Encoder # WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Third Party Libraries"},{"location":"migration/wsc53/libraries/#migrating-from-wsc-53-third-party-libraries","text":"","title":"Migrating from WSC 5.3 - Third Party Libraries"},{"location":"migration/wsc53/libraries/#guzzle","text":"The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7.","title":"Guzzle"},{"location":"migration/wsc53/libraries/#emogrifier-css-inliner","text":"The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases.","title":"Emogrifier / CSS Inliner"},{"location":"migration/wsc53/libraries/#scssphp","text":"scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details.","title":"scssphp"},{"location":"migration/wsc53/libraries/#constant-time-encoder","text":"WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Constant Time Encoder"},{"location":"migration/wsc53/php/","text":"Migrating from WSC 5.3 - PHP # Minimum requirements # The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details. Flood Control # To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class! DatabasePackageInstallationPlugin # DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file. PHP Database API # The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed. Captcha # The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details. Search # The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details. Styles # The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details. Tags # Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details. Avatars # The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"PHP API"},{"location":"migration/wsc53/php/#migrating-from-wsc-53-php","text":"","title":"Migrating from WSC 5.3 - PHP"},{"location":"migration/wsc53/php/#minimum-requirements","text":"The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details.","title":"Minimum requirements"},{"location":"migration/wsc53/php/#flood-control","text":"To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class!","title":"Flood Control"},{"location":"migration/wsc53/php/#databasepackageinstallationplugin","text":"DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file.","title":"DatabasePackageInstallationPlugin"},{"location":"migration/wsc53/php/#php-database-api","text":"The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed.","title":"PHP Database API"},{"location":"migration/wsc53/php/#captcha","text":"The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details.","title":"Captcha"},{"location":"migration/wsc53/php/#search","text":"The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details.","title":"Search"},{"location":"migration/wsc53/php/#styles","text":"The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details.","title":"Styles"},{"location":"migration/wsc53/php/#tags","text":"Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details.","title":"Tags"},{"location":"migration/wsc53/php/#avatars","text":"The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"Avatars"},{"location":"migration/wsc53/session/","text":"Migrating from WSC 5.3 - Session Handling and Authentication # WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data. Summary and Concepts # Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other. Legacy Persistent Login # The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight. Multiple Sessions # To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler . Merged ACP and Frontend Sessions # WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa. Improved Authentication and Reauthentication # WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence. Additions and Changes # Password Hashing # WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems. Session Storage # The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class! Reauthentication # For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 . Multi-factor Authentication # To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in. Adding Multi-factor Methods # Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions. Deprecations and Removals # SessionHandler # Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details. ACP Sessions # The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions. Cookies # The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed. Virtual Sessions # The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables. Security Token Constants # The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions. PasswordUtil and Double BCrypt Hashes # Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"Session Handling and Authentication"},{"location":"migration/wsc53/session/#migrating-from-wsc-53-session-handling-and-authentication","text":"WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data.","title":"Migrating from WSC 5.3 - Session Handling and Authentication"},{"location":"migration/wsc53/session/#summary-and-concepts","text":"Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other.","title":"Summary and Concepts"},{"location":"migration/wsc53/session/#legacy-persistent-login","text":"The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight.","title":"Legacy Persistent Login"},{"location":"migration/wsc53/session/#multiple-sessions","text":"To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler .","title":"Multiple Sessions"},{"location":"migration/wsc53/session/#merged-acp-and-frontend-sessions","text":"WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa.","title":"Merged ACP and Frontend Sessions"},{"location":"migration/wsc53/session/#improved-authentication-and-reauthentication","text":"WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence.","title":"Improved Authentication and Reauthentication"},{"location":"migration/wsc53/session/#additions-and-changes","text":"","title":"Additions and Changes"},{"location":"migration/wsc53/session/#password-hashing","text":"WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems.","title":"Password Hashing"},{"location":"migration/wsc53/session/#session-storage","text":"The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class!","title":"Session Storage"},{"location":"migration/wsc53/session/#reauthentication","text":"For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 .","title":"Reauthentication"},{"location":"migration/wsc53/session/#multi-factor-authentication","text":"To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in.","title":"Multi-factor Authentication"},{"location":"migration/wsc53/session/#adding-multi-factor-methods","text":"Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions.","title":"Adding Multi-factor Methods"},{"location":"migration/wsc53/session/#deprecations-and-removals","text":"","title":"Deprecations and Removals"},{"location":"migration/wsc53/session/#sessionhandler","text":"Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details.","title":"SessionHandler"},{"location":"migration/wsc53/session/#acp-sessions","text":"The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions.","title":"ACP Sessions"},{"location":"migration/wsc53/session/#cookies","text":"The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed.","title":"Cookies"},{"location":"migration/wsc53/session/#virtual-sessions","text":"The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables.","title":"Virtual Sessions"},{"location":"migration/wsc53/session/#security-token-constants","text":"The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions.","title":"Security Token Constants"},{"location":"migration/wsc53/session/#passwordutil-and-double-bcrypt-hashes","text":"Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"PasswordUtil and Double BCrypt Hashes"},{"location":"migration/wsc53/templates/","text":"Migrating from WSC 5.3 - Templates and Languages # {csrfToken} # Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"Templates"},{"location":"migration/wsc53/templates/#migrating-from-wsc-53-templates-and-languages","text":"","title":"Migrating from WSC 5.3 - Templates and Languages"},{"location":"migration/wsc53/templates/#csrftoken","text":"Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"{csrfToken}"},{"location":"package/database-php-api/","text":"Database PHP API # Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ]; Database Tables # There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop () Columns # To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull () Foreign Keys # Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data. Indices # Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Database PHP API"},{"location":"package/database-php-api/#database-php-api","text":"Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ];","title":"Database PHP API"},{"location":"package/database-php-api/#database-tables","text":"There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop ()","title":"Database Tables"},{"location":"package/database-php-api/#columns","text":"To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull ()","title":"Columns"},{"location":"package/database-php-api/#foreign-keys","text":"Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data.","title":"Foreign Keys"},{"location":"package/database-php-api/#indices","text":"Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Indices"},{"location":"package/package-xml/","text":"package.xml # The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package> Elements # <package> # The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ). <packageinformation> # Holds the entire meta data of the package. <packagename> # This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation> <packagedescription> # Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details. <version> # The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction) <date> # Must be a valid ISO 8601 date, e.g. 2013-12-27 . <authorinformation> # Holds meta data regarding the package's author. <author> # Can be anything you want. <authorurl> # (optional) URL to the author's website. <requiredpackages> # A list of packages including their version required for this package to work. <requiredpackage> # Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml . <optionalpackage> # A list of optional packages which can be selected by the user at the very end of the installation process. <optionalpackage> # Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml . <excludedpackages> # List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed. <excludedpackage> # Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher. <compatibility> # Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility> Existing API versions # WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a <instructions> # List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first. <instructions type=\"install\"> # List of instructions for a new installation of this package. <instructions type=\"update\" fromversion=\"\u2026\"> # The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 . <instruction> # Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" /> <void/> # Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"package.xml"},{"location":"package/package-xml/#packagexml","text":"The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version.","title":"package.xml"},{"location":"package/package-xml/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package>","title":"Example"},{"location":"package/package-xml/#elements","text":"","title":"Elements"},{"location":"package/package-xml/#package","text":"The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ).","title":"&lt;package&gt;"},{"location":"package/package-xml/#packageinformation","text":"Holds the entire meta data of the package.","title":"&lt;packageinformation&gt;"},{"location":"package/package-xml/#packagename","text":"This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation>","title":"&lt;packagename&gt;"},{"location":"package/package-xml/#packagedescription","text":"Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details.","title":"&lt;packagedescription&gt;"},{"location":"package/package-xml/#version","text":"The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction)","title":"&lt;version&gt;"},{"location":"package/package-xml/#date","text":"Must be a valid ISO 8601 date, e.g. 2013-12-27 .","title":"&lt;date&gt;"},{"location":"package/package-xml/#authorinformation","text":"Holds meta data regarding the package's author.","title":"&lt;authorinformation&gt;"},{"location":"package/package-xml/#author","text":"Can be anything you want.","title":"&lt;author&gt;"},{"location":"package/package-xml/#authorurl","text":"(optional) URL to the author's website.","title":"&lt;authorurl&gt;"},{"location":"package/package-xml/#requiredpackages","text":"A list of packages including their version required for this package to work.","title":"&lt;requiredpackages&gt;"},{"location":"package/package-xml/#requiredpackage","text":"Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml .","title":"&lt;requiredpackage&gt;"},{"location":"package/package-xml/#optionalpackage","text":"A list of optional packages which can be selected by the user at the very end of the installation process.","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#optionalpackage_1","text":"Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml .","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#excludedpackages","text":"List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed.","title":"&lt;excludedpackages&gt;"},{"location":"package/package-xml/#excludedpackage","text":"Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher.","title":"&lt;excludedpackage&gt;"},{"location":"package/package-xml/#compatibility","text":"Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility>","title":"&lt;compatibility&gt;"},{"location":"package/package-xml/#existing-api-versions","text":"WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a","title":"Existing API versions"},{"location":"package/package-xml/#instructions","text":"List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first.","title":"&lt;instructions&gt;"},{"location":"package/package-xml/#instructions-typeinstall","text":"List of instructions for a new installation of this package.","title":"&lt;instructions type=\"install\"&gt;"},{"location":"package/package-xml/#instructions-typeupdate-fromversion","text":"The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 .","title":"&lt;instructions type=\"update\" fromversion=\"\u2026\"&gt;"},{"location":"package/package-xml/#instruction","text":"Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" />","title":"&lt;instruction&gt;"},{"location":"package/package-xml/#void","text":"Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"&lt;void/&gt;"},{"location":"package/pip/","text":"Package Installation Plugins # Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary. Built-In PIPs # Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Overview"},{"location":"package/pip/#package-installation-plugins","text":"Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary.","title":"Package Installation Plugins"},{"location":"package/pip/#built-in-pips","text":"Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Built-In PIPs"},{"location":"package/pip/acl-option/","text":"ACL Option Package Installation Plugin # Add customizable permissions for individual objects. Option Components # Each acl option is described as an <option> element with the mandatory attribute name . <categoryname> # Optional The name of the acl option category to which the option belongs. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Category Components # Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"aclOption"},{"location":"package/pip/acl-option/#acl-option-package-installation-plugin","text":"Add customizable permissions for individual objects.","title":"ACL Option Package Installation Plugin"},{"location":"package/pip/acl-option/#option-components","text":"Each acl option is described as an <option> element with the mandatory attribute name .","title":"Option Components"},{"location":"package/pip/acl-option/#categoryname","text":"Optional The name of the acl option category to which the option belongs.","title":"&lt;categoryname&gt;"},{"location":"package/pip/acl-option/#objecttype","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#category-components","text":"Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value.","title":"Category Components"},{"location":"package/pip/acl-option/#objecttype_1","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"Example"},{"location":"package/pip/acp-menu/","text":"ACP Menu Package Installation Plugin # Registers new ACP menu items. Components # Each item is described as an <acpmenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <icon> # Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"acpMenu"},{"location":"package/pip/acp-menu/#acp-menu-package-installation-plugin","text":"Registers new ACP menu items.","title":"ACP Menu Package Installation Plugin"},{"location":"package/pip/acp-menu/#components","text":"Each item is described as an <acpmenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/acp-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/acp-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/acp-menu/#icon","text":"Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class.","title":"&lt;icon&gt;"},{"location":"package/pip/acp-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/acp-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/acp-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"Example"},{"location":"package/pip/acp-search-provider/","text":"ACP Search Provider Package Installation Plugin # Registers data provider for the admin panel search. Components # Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name . <classname> # The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface. <showorder> # Optional Determines at which position of the search result list the provided results are shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"acpSearchProvider"},{"location":"package/pip/acp-search-provider/#acp-search-provider-package-installation-plugin","text":"Registers data provider for the admin panel search.","title":"ACP Search Provider Package Installation Plugin"},{"location":"package/pip/acp-search-provider/#components","text":"Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-search-provider/#classname","text":"The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/acp-search-provider/#showorder","text":"Optional Determines at which position of the search result list the provided results are shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-search-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"Example"},{"location":"package/pip/acp-template/","text":"ACP Template Installation Plugin # Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages. Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file. Attributes # application # The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory. Example in package.xml # <instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"acpTemplate"},{"location":"package/pip/acp-template/#acp-template-installation-plugin","text":"Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages.","title":"ACP Template Installation Plugin"},{"location":"package/pip/acp-template/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/acp-template/#attributes","text":"","title":"Attributes"},{"location":"package/pip/acp-template/#application","text":"The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory.","title":"application"},{"location":"package/pip/acp-template/#example-in-packagexml","text":"<instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"Example in package.xml"},{"location":"package/pip/bbcode/","text":"BBCode Package Installation Plugin # Registers new BBCodes. Components # Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user. <htmlopen> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode. <htmlclose> # Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags. <classname> # The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered. <wysiwygicon> # Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar. <buttonlabel> # Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon. <sourcecode> # Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead. <isBlockElement> # Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification). <attributes> # Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer. <html> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value. <validationpattern> # Optional Defines a regular expression that is used to validate the value of the attribute. <required> # Optional Specifies whether this attribute must be provided. <usetext> # Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"bbcode"},{"location":"package/pip/bbcode/#bbcode-package-installation-plugin","text":"Registers new BBCodes.","title":"BBCode Package Installation Plugin"},{"location":"package/pip/bbcode/#components","text":"Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user.","title":"Components"},{"location":"package/pip/bbcode/#htmlopen","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode.","title":"&lt;htmlopen&gt;"},{"location":"package/pip/bbcode/#htmlclose","text":"Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags.","title":"&lt;htmlclose&gt;"},{"location":"package/pip/bbcode/#classname","text":"The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered.","title":"&lt;classname&gt;"},{"location":"package/pip/bbcode/#wysiwygicon","text":"Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar.","title":"&lt;wysiwygicon&gt;"},{"location":"package/pip/bbcode/#buttonlabel","text":"Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon.","title":"&lt;buttonlabel&gt;"},{"location":"package/pip/bbcode/#sourcecode","text":"Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead.","title":"&lt;sourcecode&gt;"},{"location":"package/pip/bbcode/#isblockelement","text":"Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification).","title":"&lt;isBlockElement&gt;"},{"location":"package/pip/bbcode/#attributes","text":"Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer.","title":"&lt;attributes&gt;"},{"location":"package/pip/bbcode/#html","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value.","title":"&lt;html&gt;"},{"location":"package/pip/bbcode/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of the attribute.","title":"&lt;validationpattern&gt;"},{"location":"package/pip/bbcode/#required","text":"Optional Specifies whether this attribute must be provided.","title":"&lt;required&gt;"},{"location":"package/pip/bbcode/#usetext","text":"Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value.","title":"&lt;usetext&gt;"},{"location":"package/pip/bbcode/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"Example"},{"location":"package/pip/box/","text":"Box Package Installation Plugin # Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based. Components # Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity . <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <boxType> # system # The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <objectType> # Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController . <position> # The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top Placeholder Positions # --8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\" <showHeader> # Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 . <visibleEverywhere> # Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> . <visibilityExceptions> # Inverts the <visibleEverywhere> setting for the listed pages only. <cssClassName> # Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the box title shown to the end users. <content> # The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"box"},{"location":"package/pip/box/#box-package-installation-plugin","text":"Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based.","title":"Box Package Installation Plugin"},{"location":"package/pip/box/#components","text":"Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity .","title":"Components"},{"location":"package/pip/box/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/box/#boxtype","text":"","title":"&lt;boxType&gt;"},{"location":"package/pip/box/#system","text":"The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element.","title":"system"},{"location":"package/pip/box/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/box/#objecttype","text":"Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController .","title":"&lt;objectType&gt;"},{"location":"package/pip/box/#position","text":"The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top","title":"&lt;position&gt;"},{"location":"package/pip/box/#placeholder-positions","text":"--8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\"","title":"Placeholder Positions"},{"location":"package/pip/box/#showheader","text":"Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 .","title":"&lt;showHeader&gt;"},{"location":"package/pip/box/#visibleeverywhere","text":"Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> .","title":"&lt;visibleEverywhere&gt;"},{"location":"package/pip/box/#visibilityexceptions","text":"Inverts the <visibleEverywhere> setting for the listed pages only.","title":"&lt;visibilityExceptions&gt;"},{"location":"package/pip/box/#cssclassname","text":"Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance.","title":"&lt;cssClassName&gt;"},{"location":"package/pip/box/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/box/#title","text":"The title element is required and controls the box title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/box/#content_1","text":"The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/box/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"Example"},{"location":"package/pip/clipboard-action/","text":"Clipboard Action Package Installation Plugin # Registers clipboard actions. Components # Each clipboard action is described as an <action> element with the mandatory attribute name . <actionclassname> # The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction . <pages> # Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available. <showorder> # Optional Determines at which position of the clipboard action list the action is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"clipboardAction"},{"location":"package/pip/clipboard-action/#clipboard-action-package-installation-plugin","text":"Registers clipboard actions.","title":"Clipboard Action Package Installation Plugin"},{"location":"package/pip/clipboard-action/#components","text":"Each clipboard action is described as an <action> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/clipboard-action/#actionclassname","text":"The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction .","title":"&lt;actionclassname&gt;"},{"location":"package/pip/clipboard-action/#pages","text":"Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available.","title":"&lt;pages&gt;"},{"location":"package/pip/clipboard-action/#showorder","text":"Optional Determines at which position of the clipboard action list the action is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/clipboard-action/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"Example"},{"location":"package/pip/core-object/","text":"Core Object Package Installation Plugin # Registers wcf\\system\\SingletonFactory objects to be accessible in templates. Components # Each item is described as a <coreobject> element with the mandatory element objectname . <objectname> # The fully qualified class name of the class. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"coreObject"},{"location":"package/pip/core-object/#core-object-package-installation-plugin","text":"Registers wcf\\system\\SingletonFactory objects to be accessible in templates.","title":"Core Object Package Installation Plugin"},{"location":"package/pip/core-object/#components","text":"Each item is described as a <coreobject> element with the mandatory element objectname .","title":"Components"},{"location":"package/pip/core-object/#objectname","text":"The fully qualified class name of the class.","title":"&lt;objectname&gt;"},{"location":"package/pip/core-object/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"Example"},{"location":"package/pip/cronjob/","text":"Cronjob Package Installation Plugin # Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past. Components # Each cronjob is described as an <cronjob> element with the mandatory attribute name . <classname> # The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface. <description> # The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator. <start*> # All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax. <canBeEdited> # Controls whether the administrator may edit the fields of the cronjob. <canBeDisabled> # Controls whether the administrator may disable the cronjob. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"cronjob"},{"location":"package/pip/cronjob/#cronjob-package-installation-plugin","text":"Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past.","title":"Cronjob Package Installation Plugin"},{"location":"package/pip/cronjob/#components","text":"Each cronjob is described as an <cronjob> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/cronjob/#classname","text":"The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface.","title":"&lt;classname&gt;"},{"location":"package/pip/cronjob/#description","text":"The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator.","title":"&lt;description&gt;"},{"location":"package/pip/cronjob/#start","text":"All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax.","title":"&lt;start*&gt;"},{"location":"package/pip/cronjob/#canbeedited","text":"Controls whether the administrator may edit the fields of the cronjob.","title":"&lt;canBeEdited&gt;"},{"location":"package/pip/cronjob/#canbedisabled","text":"Controls whether the administrator may disable the cronjob.","title":"&lt;canBeDisabled&gt;"},{"location":"package/pip/cronjob/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/cronjob/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"Example"},{"location":"package/pip/database/","text":"Database Package Installation Plugin # Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename. Execution environment # The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#database-package-installation-plugin","text":"Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#attributes","text":"","title":"Attributes"},{"location":"package/pip/database/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/database/#expected-value","text":"The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects.","title":"Expected value"},{"location":"package/pip/database/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename.","title":"Naming convention"},{"location":"package/pip/database/#execution-environment","text":"The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Execution environment"},{"location":"package/pip/event-listener/","text":"Event Listener Package Installation Plugin # Registers event listeners. An explanation of events and event listeners can be found here . Components # Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <eventclassname> # The event class name is the name of the class in which the event is fired. <eventname> # The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events. <listenerclassname> # The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface! <inherit> # The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value. <environment> # The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ). <nice> # The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed. <permissions> # The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"eventListener"},{"location":"package/pip/event-listener/#event-listener-package-installation-plugin","text":"Registers event listeners. An explanation of events and event listeners can be found here .","title":"Event Listener Package Installation Plugin"},{"location":"package/pip/event-listener/#components","text":"Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/event-listener/#eventclassname","text":"The event class name is the name of the class in which the event is fired.","title":"&lt;eventclassname&gt;"},{"location":"package/pip/event-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events.","title":"&lt;eventname&gt;"},{"location":"package/pip/event-listener/#listenerclassname","text":"The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface!","title":"&lt;listenerclassname&gt;"},{"location":"package/pip/event-listener/#inherit","text":"The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value.","title":"&lt;inherit&gt;"},{"location":"package/pip/event-listener/#environment","text":"The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ).","title":"&lt;environment&gt;"},{"location":"package/pip/event-listener/#nice","text":"The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/event-listener/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/event-listener/#permissions","text":"The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/event-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/file/","text":"File Package Installation Plugin # Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates . Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file. Example in package.xml # <instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"file"},{"location":"package/pip/file/#file-package-installation-plugin","text":"Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates .","title":"File Package Installation Plugin"},{"location":"package/pip/file/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/file/#example-in-packagexml","text":"<instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"Example in package.xml"},{"location":"package/pip/language/","text":"Language Package Installation Plugin # Registers new language items. Components # The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute. <category> # Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). <item> # Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"language"},{"location":"package/pip/language/#language-package-installation-plugin","text":"Registers new language items.","title":"Language Package Installation Plugin"},{"location":"package/pip/language/#components","text":"The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute.","title":"Components"},{"location":"package/pip/language/#category","text":"Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E).","title":"&lt;category&gt;"},{"location":"package/pip/language/#item","text":"Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting.","title":"&lt;item&gt;"},{"location":"package/pip/language/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"Example"},{"location":"package/pip/media-provider/","text":"Media Provider Package Installation Plugin # Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages. Components # Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist . <title> # The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users. <regex> # The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group. <className> # <className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider . <html> # <className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"mediaProvider"},{"location":"package/pip/media-provider/#media-provider-package-installation-plugin","text":"Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages.","title":"Media Provider Package Installation Plugin"},{"location":"package/pip/media-provider/#components","text":"Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist .","title":"Components"},{"location":"package/pip/media-provider/#title","text":"The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users.","title":"&lt;title&gt;"},{"location":"package/pip/media-provider/#regex","text":"The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group.","title":"&lt;regex&gt;"},{"location":"package/pip/media-provider/#classname","text":"<className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider .","title":"&lt;className&gt;"},{"location":"package/pip/media-provider/#html","text":"<className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} .","title":"&lt;html&gt;"},{"location":"package/pip/media-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu-item/","text":"Menu Item Package Installation Plugin # Adds menu items to existing menus. Components # Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard . <menu> # The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml . <title> # The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values. <page> # The page that the link should point to, requires the internal identifier set by creating a page through the page.xml . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menuItem"},{"location":"package/pip/menu-item/#menu-item-package-installation-plugin","text":"Adds menu items to existing menus.","title":"Menu Item Package Installation Plugin"},{"location":"package/pip/menu-item/#components","text":"Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard .","title":"Components"},{"location":"package/pip/menu-item/#menu","text":"The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml .","title":"&lt;menu&gt;"},{"location":"package/pip/menu-item/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values.","title":"&lt;title&gt;"},{"location":"package/pip/menu-item/#page","text":"The page that the link should point to, requires the internal identifier set by creating a page through the page.xml .","title":"&lt;page&gt;"},{"location":"package/pip/menu-item/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu/","text":"Menu Package Installation Plugin # Deploy and manage menus that can be placed anywhere on the site. Components # Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu . <title> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements. <box> # The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menu"},{"location":"package/pip/menu/#menu-package-installation-plugin","text":"Deploy and manage menus that can be placed anywhere on the site.","title":"Menu Package Installation Plugin"},{"location":"package/pip/menu/#components","text":"Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu .","title":"Components"},{"location":"package/pip/menu/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements.","title":"&lt;title&gt;"},{"location":"package/pip/menu/#box","text":"The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName","title":"&lt;box&gt;"},{"location":"package/pip/menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/object-type-definition/","text":"Object Type Definition Package Installation Plugin # Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system. Components # Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <interfacename> # Optional The name of the PHP interface objectTypes have to implement. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"objectTypeDefinition"},{"location":"package/pip/object-type-definition/#object-type-definition-package-installation-plugin","text":"Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system.","title":"Object Type Definition Package Installation Plugin"},{"location":"package/pip/object-type-definition/#components","text":"Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type-definition/#interfacename","text":"Optional The name of the PHP interface objectTypes have to implement.","title":"&lt;interfacename&gt;"},{"location":"package/pip/object-type-definition/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"Example"},{"location":"package/pip/object-type/","text":"Object Type Package Installation Plugin # Registers an object type. Read about object types in the objectTypeDefinition PIP. Components # Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <definitionname> # The <name> of the objectTypeDefinition . <classname> # The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition. <*> # Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"objectType"},{"location":"package/pip/object-type/#object-type-package-installation-plugin","text":"Registers an object type. Read about object types in the objectTypeDefinition PIP.","title":"Object Type Package Installation Plugin"},{"location":"package/pip/object-type/#components","text":"Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type/#definitionname","text":"The <name> of the objectTypeDefinition .","title":"&lt;definitionname&gt;"},{"location":"package/pip/object-type/#classname","text":"The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition.","title":"&lt;classname&gt;"},{"location":"package/pip/object-type/#_1","text":"Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/object-type/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"Example"},{"location":"package/pip/option/","text":"Option Package Installation Plugin # Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants. Category Components # Each category is described as an <category> element with the mandatory attribute name . <parent> # Optional The category\u2019s parent category. <showorder> # Optional Specifies the order of this option within the parent category. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator. Option Components # Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it. <categoryname> # The option\u2019s category. <optiontype> # The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes. <defaultvalue> # The value that is set after installation of a package. Valid values are defined by the optiontype . <validationpattern> # Optional Defines a regular expression that is used to validate the value of a free form option (such as text ). <showorder> # Optional Specifies the order of this option within the category. <selectoptions> # Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code. <enableoptions> # Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option. <hidden> # Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator. <supporti18n> # Optional Specifies whether this option supports localized input. <requirei18n> # Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language). <*> # Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation. Language Items # All relevant language items have to be put into the wcf.acp.option language item category. Categories # If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"option"},{"location":"package/pip/option/#option-package-installation-plugin","text":"Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants.","title":"Option Package Installation Plugin"},{"location":"package/pip/option/#category-components","text":"Each category is described as an <category> element with the mandatory attribute name .","title":"Category Components"},{"location":"package/pip/option/#parent","text":"Optional The category\u2019s parent category.","title":"&lt;parent&gt;"},{"location":"package/pip/option/#showorder","text":"Optional Specifies the order of this option within the parent category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#option-components","text":"Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it.","title":"Option Components"},{"location":"package/pip/option/#categoryname","text":"The option\u2019s category.","title":"&lt;categoryname&gt;"},{"location":"package/pip/option/#optiontype","text":"The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes.","title":"&lt;optiontype&gt;"},{"location":"package/pip/option/#defaultvalue","text":"The value that is set after installation of a package. Valid values are defined by the optiontype .","title":"&lt;defaultvalue&gt;"},{"location":"package/pip/option/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of a free form option (such as text ).","title":"&lt;validationpattern&gt;"},{"location":"package/pip/option/#showorder_1","text":"Optional Specifies the order of this option within the category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#selectoptions","text":"Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code.","title":"&lt;selectoptions&gt;"},{"location":"package/pip/option/#enableoptions","text":"Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option.","title":"&lt;enableoptions&gt;"},{"location":"package/pip/option/#hidden","text":"Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically.","title":"&lt;hidden&gt;"},{"location":"package/pip/option/#options_1","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#supporti18n","text":"Optional Specifies whether this option supports localized input.","title":"&lt;supporti18n&gt;"},{"location":"package/pip/option/#requirei18n","text":"Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language).","title":"&lt;requirei18n&gt;"},{"location":"package/pip/option/#_1","text":"Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/option/#language-items","text":"All relevant language items have to be put into the wcf.acp.option language item category.","title":"Language Items"},{"location":"package/pip/option/#categories","text":"If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/option/#options_2","text":"If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description .","title":"Options"},{"location":"package/pip/option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/page/","text":"Page Package Installation Plugin # Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus. Components # Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList . <pageType> # system # The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <controller> # Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm . <handler> # Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id. <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <parent> # Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy. <hasFixedParent> # Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable. <permissions> # The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set. <options> # The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set. <excludeFromLandingPage> # Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the page title shown to the end users. <content> # The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"page"},{"location":"package/pip/page/#page-package-installation-plugin","text":"Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus.","title":"Page Package Installation Plugin"},{"location":"package/pip/page/#components","text":"Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList .","title":"Components"},{"location":"package/pip/page/#pagetype","text":"","title":"&lt;pageType&gt;"},{"location":"package/pip/page/#system","text":"The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element.","title":"system"},{"location":"package/pip/page/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/page/#controller","text":"Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm .","title":"&lt;controller&gt;"},{"location":"package/pip/page/#handler","text":"Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id.","title":"&lt;handler&gt;"},{"location":"package/pip/page/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/page/#parent","text":"Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy.","title":"&lt;parent&gt;"},{"location":"package/pip/page/#hasfixedparent","text":"Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable.","title":"&lt;hasFixedParent&gt;"},{"location":"package/pip/page/#permissions","text":"The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set.","title":"&lt;permissions&gt;"},{"location":"package/pip/page/#options","text":"The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set.","title":"&lt;options&gt;"},{"location":"package/pip/page/#excludefromlandingpage","text":"Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever.","title":"&lt;excludeFromLandingPage&gt;"},{"location":"package/pip/page/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/page/#title","text":"The title element is required and controls the page title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/page/#content_1","text":"The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/page/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"Example"},{"location":"package/pip/pip/","text":"Package Installation Plugin Package Installation Plugin # Registers new package installation plugins. Components # Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation! Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"pip"},{"location":"package/pip/pip/#package-installation-plugin-package-installation-plugin","text":"Registers new package installation plugins.","title":"Package Installation Plugin Package Installation Plugin"},{"location":"package/pip/pip/#components","text":"Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation!","title":"Components"},{"location":"package/pip/pip/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"Example"},{"location":"package/pip/script/","text":"Script Package Installation Plugin # Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The script -PIP expects a relative path to a .php file. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . Execution environment # The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"script"},{"location":"package/pip/script/#script-package-installation-plugin","text":"Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Script Package Installation Plugin"},{"location":"package/pip/script/#attributes","text":"","title":"Attributes"},{"location":"package/pip/script/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/script/#expected-value","text":"The script -PIP expects a relative path to a .php file.","title":"Expected value"},{"location":"package/pip/script/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 .","title":"Naming convention"},{"location":"package/pip/script/#execution-environment","text":"The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"Execution environment"},{"location":"package/pip/smiley/","text":"Smiley Package Installation Plugin # Installs new smileys. Components # Each smiley is described as an <smiley> element with the mandatory attribute name . <title> # Short human readable description of the smiley. <path(2x)?> # The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens. <aliases> # Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A). <showorder> # Optional Determines at which position of the smiley list the smiley is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"smiley"},{"location":"package/pip/smiley/#smiley-package-installation-plugin","text":"Installs new smileys.","title":"Smiley Package Installation Plugin"},{"location":"package/pip/smiley/#components","text":"Each smiley is described as an <smiley> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/smiley/#title","text":"Short human readable description of the smiley.","title":"&lt;title&gt;"},{"location":"package/pip/smiley/#path2x","text":"The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens.","title":"&lt;path(2x)?&gt;"},{"location":"package/pip/smiley/#aliases","text":"Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A).","title":"&lt;aliases&gt;"},{"location":"package/pip/smiley/#showorder","text":"Optional Determines at which position of the smiley list the smiley is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/smiley/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"Example"},{"location":"package/pip/sql/","text":"SQL Package Installation Plugin # Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard. Expected Value # The sql package installation plugin expects a relative path to a .sql file. Features # Logging # WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY Instance Number # It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 ! Table Type # WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used. Limitations # Logging # WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE . Triggers # WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script . Example # package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"sql"},{"location":"package/pip/sql/#sql-package-installation-plugin","text":"Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard.","title":"SQL Package Installation Plugin"},{"location":"package/pip/sql/#expected-value","text":"The sql package installation plugin expects a relative path to a .sql file.","title":"Expected Value"},{"location":"package/pip/sql/#features","text":"","title":"Features"},{"location":"package/pip/sql/#logging","text":"WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY","title":"Logging"},{"location":"package/pip/sql/#instance-number","text":"It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 !","title":"Instance Number"},{"location":"package/pip/sql/#table-type","text":"WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used.","title":"Table Type"},{"location":"package/pip/sql/#limitations","text":"","title":"Limitations"},{"location":"package/pip/sql/#logging_1","text":"WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE .","title":"Logging"},{"location":"package/pip/sql/#triggers","text":"WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script .","title":"Triggers"},{"location":"package/pip/sql/#example","text":"package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"Example"},{"location":"package/pip/style/","text":"Style Package Installation Plugin # Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles. Example in package.xml # <instruction type= \"style\" > style.tgz </instruction>","title":"style"},{"location":"package/pip/style/#style-package-installation-plugin","text":"Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles.","title":"Style Package Installation Plugin"},{"location":"package/pip/style/#example-in-packagexml","text":"<instruction type= \"style\" > style.tgz </instruction>","title":"Example in package.xml"},{"location":"package/pip/template-listener/","text":"Template Listener Package Installation Plugin # Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables). Components # Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <templatename> # The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners. <eventname> # The event name is the name given when the event is fired to identify different events within the same template. <templatecode> # The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention. <environment> # The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ). <nice> # Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"templateListener"},{"location":"package/pip/template-listener/#template-listener-package-installation-plugin","text":"Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables).","title":"Template Listener Package Installation Plugin"},{"location":"package/pip/template-listener/#components","text":"Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/template-listener/#templatename","text":"The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners.","title":"&lt;templatename&gt;"},{"location":"package/pip/template-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same template.","title":"&lt;eventname&gt;"},{"location":"package/pip/template-listener/#templatecode","text":"The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention.","title":"&lt;templatecode&gt;"},{"location":"package/pip/template-listener/#environment","text":"The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ).","title":"&lt;environment&gt;"},{"location":"package/pip/template-listener/#nice","text":"Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/template-listener/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/template-listener/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/template-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/template/","text":"Template Package Installation Plugin # Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"template"},{"location":"package/pip/template/#template-package-installation-plugin","text":"Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"Template Package Installation Plugin"},{"location":"package/pip/user-group-option/","text":"User Group Option Package Installation Plugin # Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <(admin|mod|user)defaultvalue> # Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS . <usersonly> # Makes the option unavailable for groups with the group type UserGroup::GUESTS . Language Items # All relevant language items have to be put into the wcf.acp.group language item category. Categories # If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"userGroupOption"},{"location":"package/pip/user-group-option/#user-group-option-package-installation-plugin","text":"Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Group Option Package Installation Plugin"},{"location":"package/pip/user-group-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-group-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-group-option/#adminmoduserdefaultvalue","text":"Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS .","title":"&lt;(admin|mod|user)defaultvalue&gt;"},{"location":"package/pip/user-group-option/#usersonly","text":"Makes the option unavailable for groups with the group type UserGroup::GUESTS .","title":"&lt;usersonly&gt;"},{"location":"package/pip/user-group-option/#language-items","text":"All relevant language items have to be put into the wcf.acp.group language item category.","title":"Language Items"},{"location":"package/pip/user-group-option/#categories","text":"If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-group-option/#options","text":"If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"Options"},{"location":"package/pip/user-menu/","text":"User Menu Package Installation Plugin # Registers new user menu items. Components # Each item is described as an <usermenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <iconclassname> # Use an icon only for top-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown. <classname> # The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"userMenu"},{"location":"package/pip/user-menu/#user-menu-package-installation-plugin","text":"Registers new user menu items.","title":"User Menu Package Installation Plugin"},{"location":"package/pip/user-menu/#components","text":"Each item is described as an <usermenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/user-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/user-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/user-menu/#iconclassname","text":"Use an icon only for top-level items. Name of the Font Awesome icon class.","title":"&lt;iconclassname&gt;"},{"location":"package/pip/user-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-menu/#classname","text":"The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"Example"},{"location":"package/pip/user-notification-event/","text":"User Notification Event Package Installation Plugin # Registers new user notification events. Components # Each package installation plugin is described as an <event> element with the mandatory child <name> . <objectType> # The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition. <classname> # The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface. <preset> # Defines whether this event is enabled by default. <presetmailnotificationtype> # Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"userNotificationEvent"},{"location":"package/pip/user-notification-event/#user-notification-event-package-installation-plugin","text":"Registers new user notification events.","title":"User Notification Event Package Installation Plugin"},{"location":"package/pip/user-notification-event/#components","text":"Each package installation plugin is described as an <event> element with the mandatory child <name> .","title":"Components"},{"location":"package/pip/user-notification-event/#objecttype","text":"The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition.","title":"&lt;objectType&gt;"},{"location":"package/pip/user-notification-event/#classname","text":"The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-notification-event/#preset","text":"Defines whether this event is enabled by default.","title":"&lt;preset&gt;"},{"location":"package/pip/user-notification-event/#presetmailnotificationtype","text":"Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default.","title":"&lt;presetmailnotificationtype&gt;"},{"location":"package/pip/user-notification-event/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available.","title":"&lt;options&gt;"},{"location":"package/pip/user-notification-event/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-notification-event/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"Example"},{"location":"package/pip/user-option/","text":"User Option Package Installation Plugin # Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <required> # Requires that a value is provided. <askduringregistration> # If set to 1 the field is shown during user registration in the frontend. <editable> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4 <visible> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8 <searchable> # If set to 1 the field is searchable. <outputclass> # PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface. Language Items # All relevant language items have to be put into the wcf.user.option language item category. Categories # If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"userOption"},{"location":"package/pip/user-option/#user-option-package-installation-plugin","text":"Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Option Package Installation Plugin"},{"location":"package/pip/user-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-option/#required","text":"Requires that a value is provided.","title":"&lt;required&gt;"},{"location":"package/pip/user-option/#askduringregistration","text":"If set to 1 the field is shown during user registration in the frontend.","title":"&lt;askduringregistration&gt;"},{"location":"package/pip/user-option/#editable","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4","title":"&lt;editable&gt;"},{"location":"package/pip/user-option/#visible","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8","title":"&lt;visible&gt;"},{"location":"package/pip/user-option/#searchable","text":"If set to 1 the field is searchable.","title":"&lt;searchable&gt;"},{"location":"package/pip/user-option/#outputclass","text":"PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface.","title":"&lt;outputclass&gt;"},{"location":"package/pip/user-option/#language-items","text":"All relevant language items have to be put into the wcf.user.option language item category.","title":"Language Items"},{"location":"package/pip/user-option/#categories","text":"If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-option/#options","text":"If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"Options"},{"location":"package/pip/user-profile-menu/","text":"User Profile Menu Package Installation Plugin # Registers new user profile tabs. Components # Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name . <classname> # The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface. <showorder> # Optional Determines at which position of the tab list the tab is shown. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"userProfileMenu"},{"location":"package/pip/user-profile-menu/#user-profile-menu-package-installation-plugin","text":"Registers new user profile tabs.","title":"User Profile Menu Package Installation Plugin"},{"location":"package/pip/user-profile-menu/#components","text":"Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-profile-menu/#classname","text":"The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-profile-menu/#showorder","text":"Optional Determines at which position of the tab list the tab is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-profile-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-profile-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-profile-menu/#example","text":"<?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"Example"},{"location":"php/apps/","text":"Apps for WoltLab Suite # Introduction # Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No. Differences to Plugins # Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link. Unique Namespace # Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" /> Unique Database Table Prefix # All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number. Separate Domain and Path Configuration # Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself. Creating an App # This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa. package.xml # The package.xml supports two additional elements in the <packageinformation> block that are unique to applications. <isapplication>1</isapplication> # This element is responsible to flag a package as an app. <applicationdirectory>example</applicationdirectory> # Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead. Minimum Required Files # An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Apps"},{"location":"php/apps/#apps-for-woltlab-suite","text":"","title":"Apps for WoltLab Suite"},{"location":"php/apps/#introduction","text":"Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No.","title":"Introduction"},{"location":"php/apps/#differences-to-plugins","text":"Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link.","title":"Differences to Plugins"},{"location":"php/apps/#unique-namespace","text":"Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" />","title":"Unique Namespace"},{"location":"php/apps/#unique-database-table-prefix","text":"All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number.","title":"Unique Database Table Prefix"},{"location":"php/apps/#separate-domain-and-path-configuration","text":"Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself.","title":"Separate Domain and Path Configuration"},{"location":"php/apps/#creating-an-app","text":"This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa.","title":"Creating an App"},{"location":"php/apps/#packagexml","text":"The package.xml supports two additional elements in the <packageinformation> block that are unique to applications.","title":"package.xml"},{"location":"php/apps/#isapplication1isapplication","text":"This element is responsible to flag a package as an app.","title":"&lt;isapplication&gt;1&lt;/isapplication&gt;"},{"location":"php/apps/#applicationdirectoryexampleapplicationdirectory","text":"Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead.","title":"&lt;applicationdirectory&gt;example&lt;/applicationdirectory&gt;"},{"location":"php/apps/#minimum-required-files","text":"An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Minimum Required Files"},{"location":"php/code-style-documentation/","text":"Documentation # The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. Database Objects # Database Table Columns as Properties # As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type Database Object Editors # Class Tags # Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself. Runtime Caches # Class Tags # Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Documentation"},{"location":"php/code-style-documentation/#documentation","text":"The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so.","title":"Documentation"},{"location":"php/code-style-documentation/#database-objects","text":"","title":"Database Objects"},{"location":"php/code-style-documentation/#database-table-columns-as-properties","text":"As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type","title":"Database Table Columns as Properties"},{"location":"php/code-style-documentation/#database-object-editors","text":"","title":"Database Object Editors"},{"location":"php/code-style-documentation/#class-tags","text":"Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself.","title":"Class Tags"},{"location":"php/code-style-documentation/#runtime-caches","text":"","title":"Runtime Caches"},{"location":"php/code-style-documentation/#class-tags_1","text":"Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Class Tags"},{"location":"php/code-style/","text":"Code Style # The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page . General Code Style # Naming conventions # The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING Arrays # For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ]; Ternary Operator # The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; Whitespaces # You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1) Classes # Referencing Class Names # If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ; Static Getters (of DatabaseObject Classes) # Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} . Long method calls # In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Code Style"},{"location":"php/code-style/#code-style","text":"The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page .","title":"Code Style"},{"location":"php/code-style/#general-code-style","text":"","title":"General Code Style"},{"location":"php/code-style/#naming-conventions","text":"The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING","title":"Naming conventions"},{"location":"php/code-style/#arrays","text":"For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ];","title":"Arrays"},{"location":"php/code-style/#ternary-operator","text":"The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ;","title":"Ternary Operator"},{"location":"php/code-style/#whitespaces","text":"You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1)","title":"Whitespaces"},{"location":"php/code-style/#classes","text":"","title":"Classes"},{"location":"php/code-style/#referencing-class-names","text":"If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ;","title":"Referencing Class Names"},{"location":"php/code-style/#static-getters-of-databaseobject-classes","text":"Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} .","title":"Static Getters (of DatabaseObject Classes)"},{"location":"php/code-style/#long-method-calls","text":"In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Long method calls"},{"location":"php/database-access/","text":"Database Access # Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works. The PreparedStatement Object # The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result } Query Parameters # The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result } Fetching a Single Result # Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor. Fetch by Column # There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn (); Fetching All Results # If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value. Building Complex Conditions # Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty! INSERT or UPDATE in Bulk # Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"Database Access"},{"location":"php/database-access/#database-access","text":"Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works.","title":"Database Access"},{"location":"php/database-access/#the-preparedstatement-object","text":"The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"The PreparedStatement Object"},{"location":"php/database-access/#query-parameters","text":"The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"Query Parameters"},{"location":"php/database-access/#fetching-a-single-result","text":"Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor.","title":"Fetching a Single Result"},{"location":"php/database-access/#fetch-by-column","text":"There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn ();","title":"Fetch by Column"},{"location":"php/database-access/#fetching-all-results","text":"If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value.","title":"Fetching All Results"},{"location":"php/database-access/#building-complex-conditions","text":"Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty!","title":"Building Complex Conditions"},{"location":"php/database-access/#insert-or-update-in-bulk","text":"Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"INSERT or UPDATE in Bulk"},{"location":"php/database-objects/","text":"Database Objects # WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text. DatabaseObject # The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually. DatabaseObjectDecorator # If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() . DatabaseObjectEditor # This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model. Create a new row # Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ; Updating an existing row # The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ; Deleting a row # Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete (); DatabaseObjectList # Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; } AbstractDatabaseObjectAction # Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; } Executing an Action # The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction Custom Method with AJAX Support # This section is about adding the method baz() to ExampleAction and calling it via AJAX. AJAX Validation # Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever! create, update and delete # The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } } Allow Invokation by Guests # Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods. ACP Access Only # Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"Database Objects"},{"location":"php/database-objects/#database-objects","text":"WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text.","title":"Database Objects"},{"location":"php/database-objects/#databaseobject","text":"The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually.","title":"DatabaseObject"},{"location":"php/database-objects/#databaseobjectdecorator","text":"If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() .","title":"DatabaseObjectDecorator"},{"location":"php/database-objects/#databaseobjecteditor","text":"This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model.","title":"DatabaseObjectEditor"},{"location":"php/database-objects/#create-a-new-row","text":"Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ;","title":"Create a new row"},{"location":"php/database-objects/#updating-an-existing-row","text":"The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ;","title":"Updating an existing row"},{"location":"php/database-objects/#deleting-a-row","text":"Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete ();","title":"Deleting a row"},{"location":"php/database-objects/#databaseobjectlist","text":"Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; }","title":"DatabaseObjectList"},{"location":"php/database-objects/#abstractdatabaseobjectaction","text":"Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; }","title":"AbstractDatabaseObjectAction"},{"location":"php/database-objects/#executing-an-action","text":"The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction","title":"Executing an Action"},{"location":"php/database-objects/#custom-method-with-ajax-support","text":"This section is about adding the method baz() to ExampleAction and calling it via AJAX.","title":"Custom Method with AJAX Support"},{"location":"php/database-objects/#ajax-validation","text":"Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever!","title":"AJAX Validation"},{"location":"php/database-objects/#create-update-and-delete","text":"The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } }","title":"create, update and delete"},{"location":"php/database-objects/#allow-invokation-by-guests","text":"Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods.","title":"Allow Invokation by Guests"},{"location":"php/database-objects/#acp-access-only","text":"Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"ACP Access Only"},{"location":"php/exceptions/","text":"Exceptions # SPL Exceptions # The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible. Custom Exceptions # Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Exceptions"},{"location":"php/exceptions/#exceptions","text":"","title":"Exceptions"},{"location":"php/exceptions/#spl-exceptions","text":"The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible.","title":"SPL Exceptions"},{"location":"php/exceptions/#custom-exceptions","text":"Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Custom Exceptions"},{"location":"php/gdpr/","text":"General Data Protection Regulation (GDPR) # Introduction # The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com. Including Data in the Export # The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property. Example code # <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } } $data # Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app. $exportUserProperties # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content. $exportUserPropertiesIfNotEmpty # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output. $exportUserOptionSettings # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content. $exportUserOptionSettingsIfNotEmpty # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. $ipAddresses # List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported. $skipUserOptions # All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"GDPR"},{"location":"php/gdpr/#general-data-protection-regulation-gdpr","text":"","title":"General Data Protection Regulation (GDPR)"},{"location":"php/gdpr/#introduction","text":"The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com.","title":"Introduction"},{"location":"php/gdpr/#including-data-in-the-export","text":"The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property.","title":"Including Data in the Export"},{"location":"php/gdpr/#example-code","text":"<? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } }","title":"Example code"},{"location":"php/gdpr/#data","text":"Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app.","title":"$data"},{"location":"php/gdpr/#exportuserproperties","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content.","title":"$exportUserProperties"},{"location":"php/gdpr/#exportuserpropertiesifnotempty","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output.","title":"$exportUserPropertiesIfNotEmpty"},{"location":"php/gdpr/#exportuseroptionsettings","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content.","title":"$exportUserOptionSettings"},{"location":"php/gdpr/#exportuseroptionsettingsifnotempty","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data.","title":"$exportUserOptionSettingsIfNotEmpty"},{"location":"php/gdpr/#ipaddresses","text":"List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported.","title":"$ipAddresses"},{"location":"php/gdpr/#skipuseroptions","text":"All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"$skipUserOptions"},{"location":"php/pages/","text":"Page Types # AbstractPage # The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow. Method Chain # __run() # This is the only method being invoked from the outside and starts the whole chain. readParameters() # Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters show() # Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show readData() # Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData assignVariables() # Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables AbstractForm # Extends the AbstractPage implementation with additional methods designed to handle form submissions properly. Method Chain # __run() # Inherited from AbstractPage. readParameters() # Inherited from AbstractPage. show() # Inherited from AbstractPage. submit() # The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit readFormParameters() # This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters validate() # Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate save() # Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save saved() # This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved readData() # Inherited from AbstractPage. assignVariables() # Inherited from AbstractPage.","title":"Pages"},{"location":"php/pages/#page-types","text":"","title":"Page Types"},{"location":"php/pages/#abstractpage","text":"The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow.","title":"AbstractPage"},{"location":"php/pages/#method-chain","text":"","title":"Method Chain"},{"location":"php/pages/#__run","text":"This is the only method being invoked from the outside and starts the whole chain.","title":"__run()"},{"location":"php/pages/#readparameters","text":"Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters","title":"readParameters()"},{"location":"php/pages/#show","text":"Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show","title":"show()"},{"location":"php/pages/#readdata","text":"Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData","title":"readData()"},{"location":"php/pages/#assignvariables","text":"Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables","title":"assignVariables()"},{"location":"php/pages/#abstractform","text":"Extends the AbstractPage implementation with additional methods designed to handle form submissions properly.","title":"AbstractForm"},{"location":"php/pages/#method-chain_1","text":"","title":"Method Chain"},{"location":"php/pages/#__run_1","text":"Inherited from AbstractPage.","title":"__run()"},{"location":"php/pages/#readparameters_1","text":"Inherited from AbstractPage.","title":"readParameters()"},{"location":"php/pages/#show_1","text":"Inherited from AbstractPage.","title":"show()"},{"location":"php/pages/#submit","text":"The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit","title":"submit()"},{"location":"php/pages/#readformparameters","text":"This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters","title":"readFormParameters()"},{"location":"php/pages/#validate","text":"Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate","title":"validate()"},{"location":"php/pages/#save","text":"Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save","title":"save()"},{"location":"php/pages/#saved","text":"This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved","title":"saved()"},{"location":"php/pages/#readdata_1","text":"Inherited from AbstractPage.","title":"readData()"},{"location":"php/pages/#assignvariables_1","text":"Inherited from AbstractPage.","title":"assignVariables()"},{"location":"php/api/caches/","text":"Caches # WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request. Understanding Caching # Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem. When to Use a Cache # It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild. When not to Use a Cache # I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"Caches"},{"location":"php/api/caches/#caches","text":"WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request.","title":"Caches"},{"location":"php/api/caches/#understanding-caching","text":"Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem.","title":"Understanding Caching"},{"location":"php/api/caches/#when-to-use-a-cache","text":"It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild.","title":"When to Use a Cache"},{"location":"php/api/caches/#when-not-to-use-a-cache","text":"I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"When not to Use a Cache"},{"location":"php/api/caches_persistent-caches/","text":"Persistent Caches # Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision. AbstractCacheBuilder # Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters ); getData(array $parameters = [], string $arrayIndex = ''): array # Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead. getMaxLifetime(): int # Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated. reset(array $parameters = []): void # Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() . rebuild(array $parameters): array # This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#persistent-caches","text":"Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#abstractcachebuilder","text":"Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters );","title":"AbstractCacheBuilder"},{"location":"php/api/caches_persistent-caches/#getdataarray-parameters-string-arrayindex-array","text":"Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead.","title":"getData(array $parameters = [], string $arrayIndex = ''): array"},{"location":"php/api/caches_persistent-caches/#getmaxlifetime-int","text":"Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated.","title":"getMaxLifetime(): int"},{"location":"php/api/caches_persistent-caches/#resetarray-parameters-void","text":"Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() .","title":"reset(array $parameters = []): void"},{"location":"php/api/caches_persistent-caches/#rebuildarray-parameters-array","text":"This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"rebuild(array $parameters): array"},{"location":"php/api/caches_runtime-caches/","text":"Runtime Caches # Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again. IRuntimeCache # Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ). Usage # <? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]); Example # <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#runtime-caches","text":"Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again.","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#iruntimecache","text":"Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ).","title":"IRuntimeCache"},{"location":"php/api/caches_runtime-caches/#usage","text":"<? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]);","title":"Usage"},{"location":"php/api/caches_runtime-caches/#example","text":"<? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Example"},{"location":"php/api/comments/","text":"Comments # User Group Options # You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above. Language Items # User Group Options # The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"Comments"},{"location":"php/api/comments/#comments","text":"","title":"Comments"},{"location":"php/api/comments/#user-group-options","text":"You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above.","title":"User Group Options"},{"location":"php/api/comments/#language-items","text":"","title":"Language Items"},{"location":"php/api/comments/#user-group-options_1","text":"The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"User Group Options"},{"location":"php/api/cronjobs/","text":"Cronjobs # Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob. Example # <? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } } ICronjob Interface # Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ). Executing Cronjobs Through CLI # Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Cronjobs"},{"location":"php/api/cronjobs/#cronjobs","text":"Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob.","title":"Cronjobs"},{"location":"php/api/cronjobs/#example","text":"<? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } }","title":"Example"},{"location":"php/api/cronjobs/#icronjob-interface","text":"Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ).","title":"ICronjob Interface"},{"location":"php/api/cronjobs/#executing-cronjobs-through-cli","text":"Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Executing Cronjobs Through CLI"},{"location":"php/api/event_list/","text":"Event List # Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name. WoltLab Suite Core # Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput * WoltLab Suite Forum # Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"Event List"},{"location":"php/api/event_list/#event-list","text":"Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name.","title":"Event List"},{"location":"php/api/event_list/#woltlab-suite-core","text":"Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput *","title":"WoltLab Suite Core"},{"location":"php/api/event_list/#woltlab-suite-forum","text":"Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"WoltLab Suite Forum"},{"location":"php/api/events/","text":"Events # WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here . Introductory Example # Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled. Listening to Events # In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty. Firing Events # If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards. Example: Using $parameters argument # Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } } Advanced Example: Additional Form Field # One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Events"},{"location":"php/api/events/#events","text":"WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here .","title":"Events"},{"location":"php/api/events/#introductory-example","text":"Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled.","title":"Introductory Example"},{"location":"php/api/events/#listening-to-events","text":"In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty.","title":"Listening to Events"},{"location":"php/api/events/#firing-events","text":"If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards.","title":"Firing Events"},{"location":"php/api/events/#example-using-parameters-argument","text":"Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } }","title":"Example: Using $parameters argument"},{"location":"php/api/events/#advanced-example-additional-form-field","text":"One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Advanced Example: Additional Form Field"},{"location":"php/api/package_installation_plugins/","text":"Package Installation Plugins # A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation. AbstractPackageInstallationPlugin # Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes. Class Members # Package Installation Plugins have a few notable class members easing your work: $installation # This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() . $application # Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored. AbstractXMLPackageInstallationPlugin # AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data. Class Members # $className # Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects. $tagName # Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object. prepareImport(array $data) # The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ]; validateImport(array $data) # The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid. findExistingItem(array $data) # The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query. 2.5.3. Example # <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ]; handleDelete(array $items) # The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); } postImport() # Allows you to (optionally) run additionally actions after all elements were processed. AbstractOptionPackageInstallationPlugin # AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options Differences to AbstractXMLPackageInstallationPlugin # $reservedTags # $reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#package-installation-plugins","text":"A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#abstractpackageinstallationplugin","text":"Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes.","title":"AbstractPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members","text":"Package Installation Plugins have a few notable class members easing your work:","title":"Class Members"},{"location":"php/api/package_installation_plugins/#installation","text":"This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() .","title":"$installation"},{"location":"php/api/package_installation_plugins/#application","text":"Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored.","title":"$application"},{"location":"php/api/package_installation_plugins/#abstractxmlpackageinstallationplugin","text":"AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data.","title":"AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members_1","text":"","title":"Class Members"},{"location":"php/api/package_installation_plugins/#classname","text":"Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects.","title":"$className"},{"location":"php/api/package_installation_plugins/#tagname","text":"Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object.","title":"$tagName"},{"location":"php/api/package_installation_plugins/#prepareimportarray-data","text":"The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ];","title":"prepareImport(array $data)"},{"location":"php/api/package_installation_plugins/#validateimportarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid.","title":"validateImport(array $data)"},{"location":"php/api/package_installation_plugins/#findexistingitemarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query.","title":"findExistingItem(array $data)"},{"location":"php/api/package_installation_plugins/#253-example","text":"<? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ];","title":"2.5.3. Example"},{"location":"php/api/package_installation_plugins/#handledeletearray-items","text":"The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); }","title":"handleDelete(array $items)"},{"location":"php/api/package_installation_plugins/#postimport","text":"Allows you to (optionally) run additionally actions after all elements were processed.","title":"postImport()"},{"location":"php/api/package_installation_plugins/#abstractoptionpackageinstallationplugin","text":"AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options","title":"AbstractOptionPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#differences-to-abstractxmlpackageinstallationplugin","text":"","title":"Differences to AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#reservedtags","text":"$reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"$reservedTags"},{"location":"php/api/sitemaps/","text":"Sitemaps # This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/sitemaps/#sitemaps","text":"This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/user_activity_points/","text":"User Activity Points # Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_activity_points/#user-activity-points","text":"Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_notifications/","text":"User Notifications # WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis. objectType.xml # For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id. userNotificationEvent.xml # Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ). Firing Events # When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs ); Marking Notifications as Confirmed # In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"User Notifications"},{"location":"php/api/user_notifications/#user-notifications","text":"WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis.","title":"User Notifications"},{"location":"php/api/user_notifications/#objecttypexml","text":"For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id.","title":"objectType.xml"},{"location":"php/api/user_notifications/#usernotificationeventxml","text":"Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ).","title":"userNotificationEvent.xml"},{"location":"php/api/user_notifications/#firing-events","text":"When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs );","title":"Firing Events"},{"location":"php/api/user_notifications/#marking-notifications-as-confirmed","text":"In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"Marking Notifications as Confirmed"},{"location":"php/api/form_builder/dependencies/","text":"Form Node Dependencies # Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript. IFormFieldDependency # The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects. Default Dependencies # WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values. JavaScript Implementation # To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract . Examples # If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Dependencies"},{"location":"php/api/form_builder/dependencies/#form-node-dependencies","text":"Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript.","title":"Form Node Dependencies"},{"location":"php/api/form_builder/dependencies/#iformfielddependency","text":"The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects.","title":"IFormFieldDependency"},{"location":"php/api/form_builder/dependencies/#default-dependencies","text":"WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values.","title":"Default Dependencies"},{"location":"php/api/form_builder/dependencies/#javascript-implementation","text":"To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract .","title":"JavaScript Implementation"},{"location":"php/api/form_builder/dependencies/#examples","text":"If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Examples"},{"location":"php/api/form_builder/form_fields/","text":"Form Builder Fields # Abstract Form Fields # The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes. AbstractFormField # AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class. AbstractNumericFormField # AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any . General Form Fields # The following form fields are general reusable fields without any underlying context. BooleanFormField # BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField . CheckboxFormField # Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox. ClassNameFormField # ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively). DateFormField # DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled. DescriptionFormField # DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label. EmailFormField # EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField . FloatFormField # FloatFormField is an implementation of AbstractNumericFormField for floating point numbers. IconFormField # IconFormField is a form field to select a FontAwesome icon. IntegerFormField # IntegerFormField is an implementation of AbstractNumericFormField for integers. IsDisabledFormField # IsDisabledFormField is a boolean form field with isDisabled as the default id. ItemListFormField # ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key. MultilineTextFormField # MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered. MultipleSelectionFormField # MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value. RadioButtonFormField # RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField . RatingFormField # RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange . ShowOrderFormField # ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering. SingleSelectionFormField # SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value. SortOrderFormField # SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending . TextFormField # TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField . TitleFormField # TitleFormField is a text form field with title as the default id and wcf.global.title as the default label. UrlFormField # UrlFormField is a text form field whose values are checked via Url::is() . Specific Fields # The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation. AclFormField # AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately. ButtonFormField # Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked. CaptchaFormField # CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method. ContentLanguageFormField # ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField . LabelFormField # LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used. OptionFormField # OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options . SimpleAclFormField # SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. SingleMediaSelectionFormField # SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set. TagFormField # TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key. UploadFormField # UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed. Provide value from database object # To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files. Process files # To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded. UserFormField # UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method. UserPasswordField # Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField UserGroupOptionFormField # UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions . UsernameFormField # UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length. Wysiwyg form container # To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container. WysiwygAttachmentFormField # WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments. WysiwygPollFormContainer # WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built. WysiwygSmileyFormContainer # WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category. WysiwygSmileyFormNode # WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer . Example # The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes () WysiwygFormField # WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. TWysiwygFormNode # All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id. Single-Use Form Fields # The following form fields are specific for certain forms and hardly reusable in other contexts. BBCodeAttributesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode. DevtoolsProjectExcludedPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project. DevtoolsProjectInstructionsFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project. DevtoolsProjectOptionalPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project. DevtoolsProjectRequiredPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"Fields"},{"location":"php/api/form_builder/form_fields/#form-builder-fields","text":"","title":"Form Builder Fields"},{"location":"php/api/form_builder/form_fields/#abstract-form-fields","text":"The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes.","title":"Abstract Form Fields"},{"location":"php/api/form_builder/form_fields/#abstractformfield","text":"AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class.","title":"AbstractFormField"},{"location":"php/api/form_builder/form_fields/#abstractnumericformfield","text":"AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any .","title":"AbstractNumericFormField"},{"location":"php/api/form_builder/form_fields/#general-form-fields","text":"The following form fields are general reusable fields without any underlying context.","title":"General Form Fields"},{"location":"php/api/form_builder/form_fields/#booleanformfield","text":"BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField .","title":"BooleanFormField"},{"location":"php/api/form_builder/form_fields/#checkboxformfield","text":"Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox.","title":"CheckboxFormField"},{"location":"php/api/form_builder/form_fields/#classnameformfield","text":"ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively).","title":"ClassNameFormField"},{"location":"php/api/form_builder/form_fields/#dateformfield","text":"DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled.","title":"DateFormField"},{"location":"php/api/form_builder/form_fields/#descriptionformfield","text":"DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label.","title":"DescriptionFormField"},{"location":"php/api/form_builder/form_fields/#emailformfield","text":"EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField .","title":"EmailFormField"},{"location":"php/api/form_builder/form_fields/#floatformfield","text":"FloatFormField is an implementation of AbstractNumericFormField for floating point numbers.","title":"FloatFormField"},{"location":"php/api/form_builder/form_fields/#iconformfield","text":"IconFormField is a form field to select a FontAwesome icon.","title":"IconFormField"},{"location":"php/api/form_builder/form_fields/#integerformfield","text":"IntegerFormField is an implementation of AbstractNumericFormField for integers.","title":"IntegerFormField"},{"location":"php/api/form_builder/form_fields/#isdisabledformfield","text":"IsDisabledFormField is a boolean form field with isDisabled as the default id.","title":"IsDisabledFormField"},{"location":"php/api/form_builder/form_fields/#itemlistformfield","text":"ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key.","title":"ItemListFormField"},{"location":"php/api/form_builder/form_fields/#multilinetextformfield","text":"MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered.","title":"MultilineTextFormField"},{"location":"php/api/form_builder/form_fields/#multipleselectionformfield","text":"MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value.","title":"MultipleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#radiobuttonformfield","text":"RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField .","title":"RadioButtonFormField"},{"location":"php/api/form_builder/form_fields/#ratingformfield","text":"RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange .","title":"RatingFormField"},{"location":"php/api/form_builder/form_fields/#showorderformfield","text":"ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering.","title":"ShowOrderFormField"},{"location":"php/api/form_builder/form_fields/#singleselectionformfield","text":"SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value.","title":"SingleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#sortorderformfield","text":"SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending .","title":"SortOrderFormField"},{"location":"php/api/form_builder/form_fields/#textformfield","text":"TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField .","title":"TextFormField"},{"location":"php/api/form_builder/form_fields/#titleformfield","text":"TitleFormField is a text form field with title as the default id and wcf.global.title as the default label.","title":"TitleFormField"},{"location":"php/api/form_builder/form_fields/#urlformfield","text":"UrlFormField is a text form field whose values are checked via Url::is() .","title":"UrlFormField"},{"location":"php/api/form_builder/form_fields/#specific-fields","text":"The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation.","title":"Specific Fields"},{"location":"php/api/form_builder/form_fields/#aclformfield","text":"AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately.","title":"AclFormField"},{"location":"php/api/form_builder/form_fields/#buttonformfield","text":"Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked.","title":"ButtonFormField"},{"location":"php/api/form_builder/form_fields/#captchaformfield","text":"CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method.","title":"CaptchaFormField"},{"location":"php/api/form_builder/form_fields/#contentlanguageformfield","text":"ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField .","title":"ContentLanguageFormField"},{"location":"php/api/form_builder/form_fields/#labelformfield","text":"LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used.","title":"LabelFormField"},{"location":"php/api/form_builder/form_fields/#optionformfield","text":"OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options .","title":"OptionFormField"},{"location":"php/api/form_builder/form_fields/#simpleaclformfield","text":"SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"SimpleAclFormField"},{"location":"php/api/form_builder/form_fields/#singlemediaselectionformfield","text":"SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set.","title":"SingleMediaSelectionFormField"},{"location":"php/api/form_builder/form_fields/#tagformfield","text":"TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key.","title":"TagFormField"},{"location":"php/api/form_builder/form_fields/#uploadformfield","text":"UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed.","title":"UploadFormField"},{"location":"php/api/form_builder/form_fields/#provide-value-from-database-object","text":"To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files.","title":"Provide value from database object"},{"location":"php/api/form_builder/form_fields/#process-files","text":"To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded.","title":"Process files"},{"location":"php/api/form_builder/form_fields/#userformfield","text":"UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method.","title":"UserFormField"},{"location":"php/api/form_builder/form_fields/#userpasswordfield","text":"Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField","title":"UserPasswordField"},{"location":"php/api/form_builder/form_fields/#usergroupoptionformfield","text":"UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions .","title":"UserGroupOptionFormField"},{"location":"php/api/form_builder/form_fields/#usernameformfield","text":"UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length.","title":"UsernameFormField"},{"location":"php/api/form_builder/form_fields/#wysiwyg-form-container","text":"To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container.","title":"Wysiwyg form container"},{"location":"php/api/form_builder/form_fields/#wysiwygattachmentformfield","text":"WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments.","title":"WysiwygAttachmentFormField"},{"location":"php/api/form_builder/form_fields/#wysiwygpollformcontainer","text":"WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built.","title":"WysiwygPollFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformcontainer","text":"WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category.","title":"WysiwygSmileyFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformnode","text":"WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer .","title":"WysiwygSmileyFormNode"},{"location":"php/api/form_builder/form_fields/#example","text":"The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes ()","title":"Example"},{"location":"php/api/form_builder/form_fields/#wysiwygformfield","text":"WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"WysiwygFormField"},{"location":"php/api/form_builder/form_fields/#twysiwygformnode","text":"All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id.","title":"TWysiwygFormNode"},{"location":"php/api/form_builder/form_fields/#single-use-form-fields","text":"The following form fields are specific for certain forms and hardly reusable in other contexts.","title":"Single-Use Form Fields"},{"location":"php/api/form_builder/form_fields/#bbcodeattributesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode.","title":"BBCodeAttributesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectexcludedpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project.","title":"DevtoolsProjectExcludedPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectinstructionsformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project.","title":"DevtoolsProjectInstructionsFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectoptionalpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project.","title":"DevtoolsProjectOptionalPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectrequiredpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"DevtoolsProjectRequiredPackagesFormField"},{"location":"php/api/form_builder/overview/","text":"Form Builder # Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar. Advantages of Form Builder # WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form. Form Builder Components # Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems. AbstractFormBuilderForm # To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object. DialogFormDocument # Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"Overview"},{"location":"php/api/form_builder/overview/#form-builder","text":"Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar.","title":"Form Builder"},{"location":"php/api/form_builder/overview/#advantages-of-form-builder","text":"WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form.","title":"Advantages of Form Builder"},{"location":"php/api/form_builder/overview/#form-builder-components","text":"Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems.","title":"Form Builder Components"},{"location":"php/api/form_builder/overview/#abstractformbuilderform","text":"To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object.","title":"AbstractFormBuilderForm"},{"location":"php/api/form_builder/overview/#dialogformdocument","text":"Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"DialogFormDocument"},{"location":"php/api/form_builder/structure/","text":"Structure of Form Builder # Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining. Form Nodes # IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label. IFormNode # IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods. IFormChildNode # IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() . IFormParentNode # IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children. IFormElement # IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element. IObjectTypeFormNode # IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods. CustomFormNode # CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used. TemplateFormNode # TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template. Form Document # A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable. Form Button # A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element. Form Container # A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container. Form Field # A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here . Form Field Interfaces and Traits # WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields: IAttributeFormField # Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used. IAutoCompleteFormField # Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens. IAutoFocusFormField # IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods. ICssClassFormField # Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods. IFileFormField # IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ). IFilterableSelectionFormField # IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods. II18nFormField # II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() . IImmutableFormField # IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable. IInputModeFormField # Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods. IMaximumFormField # IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually. IMaximumLengthFormField # IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() . IMinimumFormField # IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually. IMinimumLengthFormField # IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() . IMultipleFormField # IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually. INullableFormField # INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable. IPackagesFormField # IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods. IPatternFormField # Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods. IPlaceholderFormField # IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods. ISelectionFormField # ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods. ISuffixedFormField # ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods. TDefaultIdFormField # Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() . Displaying Forms # The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Structure"},{"location":"php/api/form_builder/structure/#structure-of-form-builder","text":"Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining.","title":"Structure of Form Builder"},{"location":"php/api/form_builder/structure/#form-nodes","text":"IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label.","title":"Form Nodes"},{"location":"php/api/form_builder/structure/#iformnode","text":"IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods.","title":"IFormNode"},{"location":"php/api/form_builder/structure/#iformchildnode","text":"IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() .","title":"IFormChildNode"},{"location":"php/api/form_builder/structure/#iformparentnode","text":"IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children.","title":"IFormParentNode"},{"location":"php/api/form_builder/structure/#iformelement","text":"IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element.","title":"IFormElement"},{"location":"php/api/form_builder/structure/#iobjecttypeformnode","text":"IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods.","title":"IObjectTypeFormNode"},{"location":"php/api/form_builder/structure/#customformnode","text":"CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used.","title":"CustomFormNode"},{"location":"php/api/form_builder/structure/#templateformnode","text":"TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template.","title":"TemplateFormNode"},{"location":"php/api/form_builder/structure/#form-document","text":"A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable.","title":"Form Document"},{"location":"php/api/form_builder/structure/#form-button","text":"A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element.","title":"Form Button"},{"location":"php/api/form_builder/structure/#form-container","text":"A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container.","title":"Form Container"},{"location":"php/api/form_builder/structure/#form-field","text":"A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here .","title":"Form Field"},{"location":"php/api/form_builder/structure/#form-field-interfaces-and-traits","text":"WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields:","title":"Form Field Interfaces and Traits"},{"location":"php/api/form_builder/structure/#iattributeformfield","text":"Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used.","title":"IAttributeFormField"},{"location":"php/api/form_builder/structure/#iautocompleteformfield","text":"Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens.","title":"IAutoCompleteFormField"},{"location":"php/api/form_builder/structure/#iautofocusformfield","text":"IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods.","title":"IAutoFocusFormField"},{"location":"php/api/form_builder/structure/#icssclassformfield","text":"Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods.","title":"ICssClassFormField"},{"location":"php/api/form_builder/structure/#ifileformfield","text":"IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ).","title":"IFileFormField"},{"location":"php/api/form_builder/structure/#ifilterableselectionformfield","text":"IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods.","title":"IFilterableSelectionFormField"},{"location":"php/api/form_builder/structure/#ii18nformfield","text":"II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() .","title":"II18nFormField"},{"location":"php/api/form_builder/structure/#iimmutableformfield","text":"IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable.","title":"IImmutableFormField"},{"location":"php/api/form_builder/structure/#iinputmodeformfield","text":"Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods.","title":"IInputModeFormField"},{"location":"php/api/form_builder/structure/#imaximumformfield","text":"IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually.","title":"IMaximumFormField"},{"location":"php/api/form_builder/structure/#imaximumlengthformfield","text":"IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() .","title":"IMaximumLengthFormField"},{"location":"php/api/form_builder/structure/#iminimumformfield","text":"IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually.","title":"IMinimumFormField"},{"location":"php/api/form_builder/structure/#iminimumlengthformfield","text":"IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() .","title":"IMinimumLengthFormField"},{"location":"php/api/form_builder/structure/#imultipleformfield","text":"IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually.","title":"IMultipleFormField"},{"location":"php/api/form_builder/structure/#inullableformfield","text":"INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable.","title":"INullableFormField"},{"location":"php/api/form_builder/structure/#ipackagesformfield","text":"IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods.","title":"IPackagesFormField"},{"location":"php/api/form_builder/structure/#ipatternformfield","text":"Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods.","title":"IPatternFormField"},{"location":"php/api/form_builder/structure/#iplaceholderformfield","text":"IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods.","title":"IPlaceholderFormField"},{"location":"php/api/form_builder/structure/#iselectionformfield","text":"ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods.","title":"ISelectionFormField"},{"location":"php/api/form_builder/structure/#isuffixedformfield","text":"ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods.","title":"ISuffixedFormField"},{"location":"php/api/form_builder/structure/#tdefaultidformfield","text":"Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() .","title":"TDefaultIdFormField"},{"location":"php/api/form_builder/structure/#displaying-forms","text":"The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Displaying Forms"},{"location":"php/api/form_builder/validation_data/","text":"Form Validation and Form Data # Form Validation # Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface. IFormFieldValidationError / FormFieldValidationError # IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method. IFormFieldValidator / FormFieldValidator # IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method. Form Data # After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler. IFormDataHandler / FormDataHandler # IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly. IFormDataProcessor / DefaultFormDataProcessor # IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array. Additional Data Processors # CustomFormDataProcessor # As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged. VoidFormDataProcessor # Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"Validation and Data"},{"location":"php/api/form_builder/validation_data/#form-validation-and-form-data","text":"","title":"Form Validation and Form Data"},{"location":"php/api/form_builder/validation_data/#form-validation","text":"Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface.","title":"Form Validation"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidationerror-formfieldvalidationerror","text":"IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method.","title":"IFormFieldValidationError / FormFieldValidationError"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidator-formfieldvalidator","text":"IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method.","title":"IFormFieldValidator / FormFieldValidator"},{"location":"php/api/form_builder/validation_data/#form-data","text":"After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler.","title":"Form Data"},{"location":"php/api/form_builder/validation_data/#iformdatahandler-formdatahandler","text":"IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly.","title":"IFormDataHandler / FormDataHandler"},{"location":"php/api/form_builder/validation_data/#iformdataprocessor-defaultformdataprocessor","text":"IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array.","title":"IFormDataProcessor / DefaultFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#additional-data-processors","text":"","title":"Additional Data Processors"},{"location":"php/api/form_builder/validation_data/#customformdataprocessor","text":"As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged.","title":"CustomFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#voidformdataprocessor","text":"Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"VoidFormDataProcessor"},{"location":"tutorial/series/overview/","text":"Tutorial Series # In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Overview"},{"location":"tutorial/series/overview/#tutorial-series","text":"In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Tutorial Series"},{"location":"tutorial/series/part_1/","text":"Tutorial Series Part 1: Base Structure # In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions. Package Functionality # The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml Person Modeling # Database Table # As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL ); Database Object # Person # In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class. PersonAction # <? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP. PersonEditor # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name. PersonList # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class. ACP # Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu. ACP Menu # We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value. People List # To list the people in the ACP, we need a PersonListPage class and a personList template. PersonListPage # <? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns. personList.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people. Person Add Form # Like the person list, the form to add new people requires a controller class and a template. PersonAddForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases. personAdd.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted. Person Edit Form # As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing. PersonEditForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\" Frontend # For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu. page.xml # First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page . menuItem.xml # Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered. People List # As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories. PersonListPage # <? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default. personList.tpl # { capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events. userGroupOption.xml # We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 . package.xml # Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"Part 1"},{"location":"tutorial/series/part_1/#tutorial-series-part-1-base-structure","text":"In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions.","title":"Tutorial Series Part 1: Base Structure"},{"location":"tutorial/series/part_1/#package-functionality","text":"The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_1/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates .","title":"Used Components"},{"location":"tutorial/series/part_1/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml","title":"Package Structure"},{"location":"tutorial/series/part_1/#person-modeling","text":"","title":"Person Modeling"},{"location":"tutorial/series/part_1/#database-table","text":"As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL );","title":"Database Table"},{"location":"tutorial/series/part_1/#database-object","text":"","title":"Database Object"},{"location":"tutorial/series/part_1/#person","text":"In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class.","title":"Person"},{"location":"tutorial/series/part_1/#personaction","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP.","title":"PersonAction"},{"location":"tutorial/series/part_1/#personeditor","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name.","title":"PersonEditor"},{"location":"tutorial/series/part_1/#personlist","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class.","title":"PersonList"},{"location":"tutorial/series/part_1/#acp","text":"Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu.","title":"ACP"},{"location":"tutorial/series/part_1/#acp-menu","text":"We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value.","title":"ACP Menu"},{"location":"tutorial/series/part_1/#people-list","text":"To list the people in the ACP, we need a PersonListPage class and a personList template.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage","text":"<? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#person-add-form","text":"Like the person list, the form to add new people requires a controller class and a template.","title":"Person Add Form"},{"location":"tutorial/series/part_1/#personaddform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases.","title":"PersonAddForm"},{"location":"tutorial/series/part_1/#personaddtpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted.","title":"personAdd.tpl"},{"location":"tutorial/series/part_1/#person-edit-form","text":"As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing.","title":"Person Edit Form"},{"location":"tutorial/series/part_1/#personeditform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\"","title":"PersonEditForm"},{"location":"tutorial/series/part_1/#frontend","text":"For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu.","title":"Frontend"},{"location":"tutorial/series/part_1/#pagexml","text":"First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page .","title":"page.xml"},{"location":"tutorial/series/part_1/#menuitemxml","text":"Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered.","title":"menuItem.xml"},{"location":"tutorial/series/part_1/#people-list_1","text":"As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage_1","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl_1","text":"{ capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#usergroupoptionxml","text":"We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 .","title":"userGroupOption.xml"},{"location":"tutorial/series/part_1/#packagexml","text":"Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_2/","text":"Part 2: Event Listeners and Template Listeners # In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date. Package Functionality # The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl Extending Person Model ( install.sql ) # The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday . Setting Birthday in ACP # To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below . Adding Birthday Table Column in ACP # To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td> Adding Birthday in Front End # In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if } templateListener.xml # The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template. eventListener.xml # There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data> package.xml # The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"Part 2"},{"location":"tutorial/series/part_2/#part-2-event-listeners-and-template-listeners","text":"In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date.","title":"Part 2: Event Listeners and Template Listeners"},{"location":"tutorial/series/part_2/#package-functionality","text":"The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_2/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events .","title":"Used Components"},{"location":"tutorial/series/part_2/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl","title":"Package Structure"},{"location":"tutorial/series/part_2/#extending-person-model-installsql","text":"The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday .","title":"Extending Person Model (install.sql)"},{"location":"tutorial/series/part_2/#setting-birthday-in-acp","text":"To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below .","title":"Setting Birthday in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-table-column-in-acp","text":"To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td>","title":"Adding Birthday Table Column in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-in-front-end","text":"In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if }","title":"Adding Birthday in Front End"},{"location":"tutorial/series/part_2/#templatelistenerxml","text":"The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template.","title":"templateListener.xml"},{"location":"tutorial/series/part_2/#eventlistenerxml","text":"There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data>","title":"eventListener.xml"},{"location":"tutorial/series/part_2/#packagexml","text":"The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_3/","text":"Tutorial Series Part 3: Person Page and Comments # In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality. Package Functionality # In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages Used Components # In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler. Package Structure # The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub . Runtime Cache # To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; } Comments # To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments. Person Page # PersonPage # <? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments. person.tpl # {capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix. page.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent. PersonPageHandler # <? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"Part 3"},{"location":"tutorial/series/part_3/#tutorial-series-part-3-person-page-and-comments","text":"In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality.","title":"Tutorial Series Part 3: Person Page and Comments"},{"location":"tutorial/series/part_3/#package-functionality","text":"In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages","title":"Package Functionality"},{"location":"tutorial/series/part_3/#used-components","text":"In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler.","title":"Used Components"},{"location":"tutorial/series/part_3/#package-structure","text":"The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub .","title":"Package Structure"},{"location":"tutorial/series/part_3/#runtime-cache","text":"To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; }","title":"Runtime Cache"},{"location":"tutorial/series/part_3/#comments","text":"To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments.","title":"Comments"},{"location":"tutorial/series/part_3/#person-page","text":"","title":"Person Page"},{"location":"tutorial/series/part_3/#personpage","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments.","title":"PersonPage"},{"location":"tutorial/series/part_3/#persontpl","text":"{capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix.","title":"person.tpl"},{"location":"tutorial/series/part_3/#pagexml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent.","title":"page.xml"},{"location":"tutorial/series/part_3/#personpagehandler","text":"<? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"PersonPageHandler"},{"location":"view/css/","text":"CSS # SCSS and CSS # SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code. File Location # Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss . Variables # You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} . Linking images # Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash. Media Breakpoints # Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } } Available Breakpoints # Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px) Asset Preloading # WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"CSS"},{"location":"view/css/#css","text":"","title":"CSS"},{"location":"view/css/#scss-and-css","text":"SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code.","title":"SCSS and CSS"},{"location":"view/css/#file-location","text":"Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss .","title":"File Location"},{"location":"view/css/#variables","text":"You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} .","title":"Variables"},{"location":"view/css/#linking-images","text":"Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash.","title":"Linking images"},{"location":"view/css/#media-breakpoints","text":"Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } }","title":"Media Breakpoints"},{"location":"view/css/#available-breakpoints","text":"Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px)","title":"Available Breakpoints"},{"location":"view/css/#asset-preloading","text":"WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"Asset Preloading"},{"location":"view/languages-naming-conventions/","text":"Language Naming Conventions # This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments Forms # Fields # If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description . Error Texts # If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items. Naming Conventions # If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type. Confirmation messages # If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items. Type-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen? English # Do you really want delete the {element type}? Example: Do you really want delete the icon? Object-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen? English # Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>? User Group Options # Comments # German # group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren English # group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#language-naming-conventions","text":"This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#forms","text":"","title":"Forms"},{"location":"view/languages-naming-conventions/#fields","text":"If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description .","title":"Fields"},{"location":"view/languages-naming-conventions/#error-texts","text":"If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items.","title":"Error Texts"},{"location":"view/languages-naming-conventions/#naming-conventions","text":"If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type.","title":"Naming Conventions"},{"location":"view/languages-naming-conventions/#confirmation-messages","text":"If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items.","title":"Confirmation messages"},{"location":"view/languages-naming-conventions/#type-specific-deletion-confirmation-message","text":"","title":"Type-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english","text":"Do you really want delete the {element type}? Example: Do you really want delete the icon?","title":"English"},{"location":"view/languages-naming-conventions/#object-specific-deletion-confirmation-message","text":"","title":"Object-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german_1","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english_1","text":"Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>?","title":"English"},{"location":"view/languages-naming-conventions/#user-group-options","text":"","title":"User Group Options"},{"location":"view/languages-naming-conventions/#comments","text":"","title":"Comments"},{"location":"view/languages-naming-conventions/#german_2","text":"group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren","title":"German"},{"location":"view/languages-naming-conventions/#english_2","text":"group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"English"},{"location":"view/languages/","text":"Languages # WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items . Special Phrases # wcf.date.dateFormat # Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation. wcf.date.timeFormat # Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters. wcf.date.firstDayOfTheWeek # Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday wcf.global.pageDirection - RTL support # Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"Languages"},{"location":"view/languages/#languages","text":"WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items .","title":"Languages"},{"location":"view/languages/#special-phrases","text":"","title":"Special Phrases"},{"location":"view/languages/#wcfdatedateformat","text":"Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation.","title":"wcf.date.dateFormat"},{"location":"view/languages/#wcfdatetimeformat","text":"Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters.","title":"wcf.date.timeFormat"},{"location":"view/languages/#wcfdatefirstdayoftheweek","text":"Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday","title":"wcf.date.firstDayOfTheWeek"},{"location":"view/languages/#wcfglobalpagedirection-rtl-support","text":"Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"wcf.global.pageDirection - RTL support"},{"location":"view/template-plugins/","text":"Template Plugins # 5.3+ anchor # The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink . 5.3+ anchorAttributes # anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default append # If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array. assign # New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } capture # In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture } concat # concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * } counter # counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default 5.4+ csrfToken # {csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details. currency # currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * } cycle # cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter date # date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date } 3.1+ dateInterval # dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set) encodeJS # encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script> encodeJSON # encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } ' escapeCDATA # escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]> event # event provides extension points in templates that template listeners can use. { event name = 'foo' } fetch # fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * } filesizeBinary # filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary } filesize # filesize formats the filesize using filesize (in bytes). { $filesize | filesize } hascontent # In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent } htmlCheckboxes # htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options htmlOptions # htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element. implode # implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration 5.2+ ipSearch # ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch } 3.0+ js # js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * } 5.3+ jslang # jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); lang # lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item. language # language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language } link # link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() . newlineToBreak # newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak } 3.0+ page # page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID ) pages # pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links plainTime # plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime } 5.3+ plural # plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer. prepend # If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array. shortUnit # shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * } smallpages # smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links. tableWordwrap # tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap } time # time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * } truncate # truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default 5.3+ user # user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"Template Plugins"},{"location":"view/template-plugins/#template-plugins","text":"","title":"Template Plugins"},{"location":"view/template-plugins/#53-anchor","text":"The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink .","title":"5.3+ anchor"},{"location":"view/template-plugins/#53-anchorattributes","text":"anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default","title":"5.3+ anchorAttributes"},{"location":"view/template-plugins/#append","text":"If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array.","title":"append"},{"location":"view/template-plugins/#assign","text":"New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * }","title":"assign"},{"location":"view/template-plugins/#capture","text":"In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture }","title":"capture"},{"location":"view/template-plugins/#concat","text":"concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * }","title":"concat"},{"location":"view/template-plugins/#counter","text":"counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default","title":"counter"},{"location":"view/template-plugins/#54-csrftoken","text":"{csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details.","title":"5.4+ csrfToken"},{"location":"view/template-plugins/#currency","text":"currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * }","title":"currency"},{"location":"view/template-plugins/#cycle","text":"cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter","title":"cycle"},{"location":"view/template-plugins/#date","text":"date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date }","title":"date"},{"location":"view/template-plugins/#31-dateinterval","text":"dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set)","title":"3.1+ dateInterval"},{"location":"view/template-plugins/#encodejs","text":"encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script>","title":"encodeJS"},{"location":"view/template-plugins/#encodejson","text":"encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } '","title":"encodeJSON"},{"location":"view/template-plugins/#escapecdata","text":"escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]>","title":"escapeCDATA"},{"location":"view/template-plugins/#event","text":"event provides extension points in templates that template listeners can use. { event name = 'foo' }","title":"event"},{"location":"view/template-plugins/#fetch","text":"fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * }","title":"fetch"},{"location":"view/template-plugins/#filesizebinary","text":"filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary }","title":"filesizeBinary"},{"location":"view/template-plugins/#filesize","text":"filesize formats the filesize using filesize (in bytes). { $filesize | filesize }","title":"filesize"},{"location":"view/template-plugins/#hascontent","text":"In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent }","title":"hascontent"},{"location":"view/template-plugins/#htmlcheckboxes","text":"htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options","title":"htmlCheckboxes"},{"location":"view/template-plugins/#htmloptions","text":"htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element.","title":"htmlOptions"},{"location":"view/template-plugins/#implode","text":"implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration","title":"implode"},{"location":"view/template-plugins/#52-ipsearch","text":"ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch }","title":"5.2+ ipSearch"},{"location":"view/template-plugins/#30-js","text":"js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * }","title":"3.0+ js"},{"location":"view/template-plugins/#53-jslang","text":"jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } );","title":"5.3+ jslang"},{"location":"view/template-plugins/#lang","text":"lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item.","title":"lang"},{"location":"view/template-plugins/#language","text":"language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language }","title":"language"},{"location":"view/template-plugins/#link","text":"link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() .","title":"link"},{"location":"view/template-plugins/#newlinetobreak","text":"newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak }","title":"newlineToBreak"},{"location":"view/template-plugins/#30-page","text":"page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID )","title":"3.0+ page"},{"location":"view/template-plugins/#pages","text":"pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links","title":"pages"},{"location":"view/template-plugins/#plaintime","text":"plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime }","title":"plainTime"},{"location":"view/template-plugins/#53-plural","text":"plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer.","title":"5.3+ plural"},{"location":"view/template-plugins/#prepend","text":"If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array.","title":"prepend"},{"location":"view/template-plugins/#shortunit","text":"shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * }","title":"shortUnit"},{"location":"view/template-plugins/#smallpages","text":"smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links.","title":"smallpages"},{"location":"view/template-plugins/#tablewordwrap","text":"tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap }","title":"tableWordwrap"},{"location":"view/template-plugins/#time","text":"time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * }","title":"time"},{"location":"view/template-plugins/#truncate","text":"truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default","title":"truncate"},{"location":"view/template-plugins/#53-user","text":"user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"5.3+ user"},{"location":"view/templates/","text":"Templates # Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore. Template Types and Conventions # WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal. Installing Templates # Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages. Base Templates # Frontend # { include file = 'header' } { * content * } { include file = 'footer' } Backend # { include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page. Common Template Components # Forms # For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> Tab Menus # <div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div> Template Scripting # Template Variables # Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]); Modifiers # If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed. System Template Variable # The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend). Comments # Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template. Conditions # Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if } Foreach Loops # Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach } Including Other Templates # To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' } Template Plugins # An overview of all available template plugins can be found here .","title":"Templates"},{"location":"view/templates/#templates","text":"Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore.","title":"Templates"},{"location":"view/templates/#template-types-and-conventions","text":"WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal.","title":"Template Types and Conventions"},{"location":"view/templates/#installing-templates","text":"Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages.","title":"Installing Templates"},{"location":"view/templates/#base-templates","text":"","title":"Base Templates"},{"location":"view/templates/#frontend","text":"{ include file = 'header' } { * content * } { include file = 'footer' }","title":"Frontend"},{"location":"view/templates/#backend","text":"{ include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page.","title":"Backend"},{"location":"view/templates/#common-template-components","text":"","title":"Common Template Components"},{"location":"view/templates/#forms","text":"For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form>","title":"Forms"},{"location":"view/templates/#tab-menus","text":"<div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div>","title":"Tab Menus"},{"location":"view/templates/#template-scripting","text":"","title":"Template Scripting"},{"location":"view/templates/#template-variables","text":"Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]);","title":"Template Variables"},{"location":"view/templates/#modifiers","text":"If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed.","title":"Modifiers"},{"location":"view/templates/#system-template-variable","text":"The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend).","title":"System Template Variable"},{"location":"view/templates/#comments","text":"Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template.","title":"Comments"},{"location":"view/templates/#conditions","text":"Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if }","title":"Conditions"},{"location":"view/templates/#foreach-loops","text":"Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach }","title":"Foreach Loops"},{"location":"view/templates/#including-other-templates","text":"To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' }","title":"Including Other Templates"},{"location":"view/templates/#template-plugins","text":"An overview of all available template plugins can be found here .","title":"Template Plugins"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"WoltLab Suite 5.4 Documentation # Introduction # This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more. About WoltLab Suite # WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"WoltLab Suite 5.4 Documentation"},{"location":"#woltlab-suite-54-documentation","text":"","title":"WoltLab Suite 5.4 Documentation"},{"location":"#introduction","text":"This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with PHP , object-oriented programming and MySQL . Head over to the quick start tutorial to learn more.","title":"Introduction"},{"location":"#about-woltlab-suite","text":"WoltLab Suite Core as well as most of the other packages are available on GitHub and are licensed under the terms of the GNU Lesser General Public License 2.1 .","title":"About WoltLab Suite"},{"location":"getting-started/","text":"Creating a simple package # Setup and Requirements # This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows The package.xml File # We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is. The PHP Class # The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all. The Template # Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance. The Page Definition # The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus. Building the Package # If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition. Installation # Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package! Developer Tools # This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed. Registering a Project # Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml . Synchronizing # The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed. Appendix # Template Guessing # The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Getting Started"},{"location":"getting-started/#creating-a-simple-package","text":"","title":"Creating a simple package"},{"location":"getting-started/#setup-and-requirements","text":"This guide will help you to create a simple package that provides a simple test page. It is nothing too fancy, but you can use it as the foundation for your next project. There are some requirements you should met before starting: Text editor with syntax highlighting for PHP, Notepad++ is a solid pick *.php and *.tpl should be encoded with ANSI/ASCII *.xml are always encoded with UTF-8, but omit the BOM (byte-order-mark) Use tabs instead of spaces to indent lines It is recommended to set the tab width to 8 spaces, this is used in the entire software and will ease reading the source files An active installation of WoltLab Suite 3 An application to create *.tar archives, e.g. 7-Zip on Windows","title":"Setup and Requirements"},{"location":"getting-started/#the-packagexml-file","text":"We want to create a simple page that will display the sentence \"Hello World\" embedded into the application frame. Create an empty directory in the workspace of your choice to start with. Create a new file called package.xml and insert the code below: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" name= \"com.example.test\" > <packageinformation> <!-- com.example.test --> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2019-04-28 </date> </packageinformation> <authorinformation> <author> Your Name </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" /> <instruction type= \"page\" /> </instructions> </package> There is an entire chapter on the package system that explains what the code above does and how you can adjust it to fit your needs. For now we'll keep it as it is.","title":"The package.xml File"},{"location":"getting-started/#the-php-class","text":"The next step is to create the PHP class which will serve our page: Create the directory files in the same directory where package.xml is located Open files and create the directory lib Open lib and create the directory page Within the directory page , please create the file TestPage.class.php Copy and paste the following code into the TestPage.class.php : <? php namespace wcf\\page ; use wcf\\system\\WCF ; /** * A simple test page for demonstration purposes. * * @author YOUR NAME * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> */ class TestPage extends AbstractPage { /** * @var string */ protected $greet = '' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_GET [ 'greet' ])) $this -> greet = $_GET [ 'greet' ]; } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $this -> greet )) { $this -> greet = 'World' ; } } /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'greet' => $this -> greet ]); } } The class inherits from wcf\\page\\AbstractPage , the default implementation of pages without form controls. It defines quite a few methods that will be automatically invoked in a specific order, for example readParameters() before readData() and finally assignVariables() to pass arbitrary values to the template. The property $greet is defined as World , but can optionally be populated through a GET variable ( index.php?test/&greet=You would output Hello You! ). This extra code illustrates the separation of data processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not start reading user input at random places, including the risk to only escape the input of variable $_GET['foo'] 4 out of 5 times. Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your namespace and class name, you can read more about it later . Last but not least, you must not include the closing PHP tag ?> at the end, it can cause PHP to break on whitespaces and is not required at all.","title":"The PHP Class"},{"location":"getting-started/#the-template","text":"Navigate back to the root directory of your package until you see both the files directory and the package.xml . Now create a directory called templates , open it and create the file test.tpl . { include file = 'header' } <div class=\"section\"> Hello { $greet } ! </div> { include file = 'footer' } Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase Hello World! in the application frame, just as any other page would render. The included templates header and footer are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance.","title":"The Template"},{"location":"getting-started/#the-page-definition","text":"The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file page.xml in your project's root directory, thus on the same level as the package.xml . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.example.test.Test\" > <controller> wcf\\page\\TestPage </controller> <name language= \"en\" > Test Page </name> <pageType> system </pageType> </page> </import> </data> You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus.","title":"The Page Definition"},{"location":"getting-started/#building-the-package","text":"If you have followed the above guidelines carefully, your package directory should now look like this: \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 TestPage.class.php \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 test.tpl Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive files.tar and add the contents of the files/* directory, but not the directory files/ itself. Repeat the same process for the templates directory, but this time with the file name templates.tar . Place both files in the root of your project. Last but not least, create the package archive com.example.test.tar and add all the files listed below. files.tar package.xml page.xml templates.tar The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition.","title":"Building the Package"},{"location":"getting-started/#installation","text":"Open the Administration Control Panel and navigate to Configuration > Packages > Install Package , click on Upload Package and select the file com.example.test.tar from your disk. Follow the on-screen instructions until it has been successfully installed. Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at https://example.com/wsc/ , then the URL should read https://example.com/wsc/index.php?test/ . Congratulations, you have just created your first package!","title":"Installation"},{"location":"getting-started/#developer-tools","text":"This feature is available with WoltLab Suite 3.1 or newer only. The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed.","title":"Developer Tools"},{"location":"getting-started/#registering-a-project","text":"Projects require the absolute path to the package directory, that is, the directory where it can find the package.xml . It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the package.xml .","title":"Registering a Project"},{"location":"getting-started/#synchronizing","text":"The install instructions in the package.xml are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface wcf\\system\\devtools\\pip\\IIdempotentPackageInstallationPlugin are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. Some built-in PIPs, such as sql or script , do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed.","title":"Synchronizing"},{"location":"getting-started/#appendix","text":"","title":"Appendix"},{"location":"getting-started/#template-guessing","text":"The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name wcf\\page\\TestPage that is then split into four distinct parts: wcf , the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) \\page\\ (ignored) Test , the actual name that is used for both the template and the URL Page (page type, ignored) The fragments 1. and 3. from above are used to construct the path to the template: <installDirOfWSC>/templates/test.tpl (the first letter of Test is being converted to lower-case).","title":"Template Guessing"},{"location":"javascript/code-snippets/","text":"Code Snippets - JavaScript API # This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function. ImageViewer # The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"Code Snippets"},{"location":"javascript/code-snippets/#code-snippets-javascript-api","text":"This is a list of code snippets that do not fit into any of the other articles and merely describe how to achieve something very specific, rather than explaining the inner workings of a function.","title":"Code Snippets - JavaScript API"},{"location":"javascript/code-snippets/#imageviewer","text":"The ImageViewer is available on all frontend pages by default, you can easily add images to the viewer by wrapping the thumbnails with a link with the CSS class jsImageViewer that points to the full version. < a href = \"http://example.com/full.jpg\" class = \"jsImageViewer\" > < img src = \"http://example.com/thumbnail.jpg\" > </ a >","title":"ImageViewer"},{"location":"javascript/general-usage/","text":"General JavaScript Usage # The History of the Legacy API # The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base. Embedding JavaScript inside Templates # The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script > Including External JavaScript Files # The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about. Debug-Variants and Cache-Buster # Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The Accelerated Guest View (\"Tiny Builds\") # You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script > The {js} Template Plugin # The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"General Usage"},{"location":"javascript/general-usage/#general-javascript-usage","text":"","title":"General JavaScript Usage"},{"location":"javascript/general-usage/#the-history-of-the-legacy-api","text":"The WoltLab Suite 3.0 introduced a new API based on AMD-Modules with ES5-JavaScript that was designed with high performance and visible dependencies in mind. This was a fundamental change in comparison to the legacy API that was build many years before while jQuery was still a thing and we had to deal with ancient browsers such as Internet Explorer 9 that felt short in both CSS and JavaScript capabilities. Fast forward a few years, the old API is still around and most important, it is actively being used by some components that have not been rewritten yet. This has been done to preserve the backwards-compatibility and to avoid the significant amount of work that it requires to rewrite a component. The components invoked on page initialization have all been rewritten to use the modern API, but some deferred objects that are invoked later during the page runtime may still use the old API. However, the legacy API is deprecated and you should not rely on it for new components at all. It slowly but steadily gets replaced up until a point where its last bits are finally removed from the code base.","title":"The History of the Legacy API"},{"location":"javascript/general-usage/#embedding-javascript-inside-templates","text":"The <script> -tags are extracted and moved during template processing, eventually placing them at the very end of the body element while preserving their order of appearance. This behavior is controlled through the data-relocate=\"true\" attribute on the <script> which is mandatory for almost all scripts, mostly because their dependencies (such as jQuery) are moved to the bottom anyway. < script data-relocate = \"true\" > $ ( function () { // Code that uses jQuery (Legacy API) }); </ script > <!-- or --> < script data-relocate = \"true\" > require ([ \"Some\" , \"Dependencies\" ], function ( Some , Dependencies ) { // Modern API }); </ script >","title":"Embedding JavaScript inside Templates"},{"location":"javascript/general-usage/#including-external-javascript-files","text":"The AMD-Modules used in the new API are automatically recognized and lazy-loaded on demand, so unless you have a rather large and pre-compiled code-base, there is nothing else to worry about.","title":"Including External JavaScript Files"},{"location":"javascript/general-usage/#debug-variants-and-cache-buster","text":"Your JavaScript files may change over time and you would want the users' browsers to always load and use the latest version of your files. This can be achieved by appending the special LAST_UPDATE_TIME constant to your file path. It contains the unix timestamp of the last time any package was installed, updated or removed and thus avoid outdated caches by relying on a unique value, without invalidating the cache more often that it needs to be. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App.js?t={@LAST_UPDATE_TIME}\" ></ script > For small scripts you can simply serve the full, non-minified version to the user at all times, the differences in size and execution speed are insignificant and are very unlikely to offer any benefits. They might even yield a worse performance, because you'll have to include them statically in the template, even if the code is never called. However, if you're including a minified build in your app or plugin, you should include a switch to load the uncompressed version in the debug mode, while serving the minified and optimized file to the average visitor. You should use the ENABLE_DEBUG_MODE constant to decide which version should be loaded. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"Debug-Variants and Cache-Buster"},{"location":"javascript/general-usage/#the-accelerated-guest-view-tiny-builds","text":"You can learn more on the Accelerated Guest View in the migration docs. The \"Accelerated Guest View\" was introduced in WoltLab Suite 3.1 and aims to decrease page size and to improve responsiveness by enabling a read-only mode for visitors. If you are providing a separate compiled build for this mode, you'll need to include yet another switch to serve the right version to the visitor. < script data-relocate = \"true\" src = \"{@$__wcf->getPath('app')}js/App{if !ENABLE_DEBUG_MODE}{if VISITOR_USE_TINY_BUILD}.tiny{/if}.min{/if}.js?t={@LAST_UPDATE_TIME}\" ></ script >","title":"The Accelerated Guest View (\"Tiny Builds\")"},{"location":"javascript/general-usage/#the-js-template-plugin","text":"The {js} template plugin exists solely to provide a much easier and less error-prone method to include external JavaScript files. {js application='app' file='App' hasTiny=true} The hasTiny attribute is optional, you can set it to false or just omit it entirely if you do not provide a tiny build for your file.","title":"The {js} Template Plugin"},{"location":"javascript/helper-functions/","text":"JavaScript Helper Functions # Introduction # Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction. Elements # elCreate(tagName: string): Element # Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" ); elRemove(element: Element) # Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element ); elShow(element: Element) # Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" ); elHide(element: Element) # Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" ); elToggle(element: Element) # Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() . Attributes # elAttr(element: Element, attribute: string, value?: string): string # Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" ); elAttrBool(element: Element, attribute: string): boolean # Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish } elData(element: Element, attribute: string, value?: string): string # Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" ); elDataBool(element: Element, attribute: string): boolean # Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish } Selecting Elements # Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error. elById(id: string): Element | null # Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" ); elBySel(selector: string, context?: Element): Element | null # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" ); elBySelAll(selector: string, context?: Element, callback: (element: Element) => void): NodeList # The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" ); Callback to Iterate Over Elements # elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element }); elClosest(element: Element, selector: string): Element | null # Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" ); Text Nodes # If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector. elByClass(className: string, context?: Element): NodeList # Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" ); elByTag(tagName: string, context?: Element): NodeList # Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" ); Utility Functions # `elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null`` # Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function. String Extensions # hashCode(): string # Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"Helper Functions"},{"location":"javascript/helper-functions/#javascript-helper-functions","text":"","title":"JavaScript Helper Functions"},{"location":"javascript/helper-functions/#introduction","text":"Since version 3.0, WoltLab Suite ships with a set of global helper functions that are exposed on the window -object and thus are available regardless of the context. They are meant to reduce code repetition and to increase readability by moving potentially relevant parts to the front of an instruction.","title":"Introduction"},{"location":"javascript/helper-functions/#elements","text":"","title":"Elements"},{"location":"javascript/helper-functions/#elcreatetagname-string-element","text":"Creates a new element with the provided tag name. var element = elCreate ( \"div\" ); // equals var element = document . createElement ( \"div\" );","title":"elCreate(tagName: string): Element"},{"location":"javascript/helper-functions/#elremoveelement-element","text":"Removes an element from its parent without returning it. This function will throw an error if the element doesn't have a parent node. elRemove ( element ); // equals element . parentNode . removeChild ( element );","title":"elRemove(element: Element)"},{"location":"javascript/helper-functions/#elshowelement-element","text":"Attempts to show an element by removing the display CSS-property, usually used in conjunction with the elHide() function. elShow ( element ); // equals element . style . removeProperty ( \"display\" );","title":"elShow(element: Element)"},{"location":"javascript/helper-functions/#elhideelement-element","text":"Attempts to hide an element by setting the display CSS-property to none , this is intended to be used with elShow() that relies on this behavior. elHide ( element ); // equals element . style . setProperty ( \"display\" , \"none\" , \"\" );","title":"elHide(element: Element)"},{"location":"javascript/helper-functions/#eltoggleelement-element","text":"Attempts to toggle the visibility of an element by examining the value of the display CSS-property and calls either elShow() or elHide() .","title":"elToggle(element: Element)"},{"location":"javascript/helper-functions/#attributes","text":"","title":"Attributes"},{"location":"javascript/helper-functions/#elattrelement-element-attribute-string-value-string-string","text":"Sets or reads an attribute value, value are implicitly casted into strings and reading non-existing attributes will always yield an empty string. If you want to test for attribute existence, you'll have to fall-back to the native Element.hasAttribute() method. You should read and set native attributes directly, such as img.src rather than img.getAttribute(\"src\"); . var value = elAttr ( element , \"some-attribute\" ); // equals var value = element . getAttribute ( \"some-attribute\" ); elAttr ( element , \"some-attribute\" , \"some value\" ); // equals element . setAttribute ( \"some-attribute\" , \"some value\" );","title":"elAttr(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#elattrboolelement-element-attribute-string-boolean","text":"Reads an attribute and converts it value into a boolean value, the strings \"1\" and \"true\" will evaluate to true . All other values, including a missing attribute, will return false . if ( elAttrBool ( element , \"some-attribute\" )) { // attribute is true-ish }","title":"elAttrBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#eldataelement-element-attribute-string-value-string-string","text":"Short-hand function to read or set HTML5 data-* -attributes, it essentially prepends the data- prefix before forwarding the call to elAttr() . var value = elData ( element , \"some-attribute\" ); // equals var value = elAttr ( element , \"data-some-attribute\" ); elData ( element , \"some-attribute\" , \"some value\" ); // equals elAttr ( element , \"data-some-attribute\" , \"some value\" );","title":"elData(element: Element, attribute: string, value?: string): string"},{"location":"javascript/helper-functions/#eldataboolelement-element-attribute-string-boolean","text":"Short-hand function to convert a HTML5 data-* -attribute into a boolean value. It prepends the data- prefix before forwarding the call to elAttrBool() . if ( elDataBool ( element , \"some-attribute\" )) { // attribute is true-ish } // equals if ( elAttrBool ( element , \"data-some-attribute\" )) { // attribute is true-ish }","title":"elDataBool(element: Element, attribute: string): boolean"},{"location":"javascript/helper-functions/#selecting-elements","text":"Unlike libraries like jQuery, these functions will return null if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for null will yield an error.","title":"Selecting Elements"},{"location":"javascript/helper-functions/#elbyidid-string-element-null","text":"Selects an element by its id -attribute value. var element = elById ( \"my-awesome-element\" ); // equals var element = document . getElementById ( \"my-awesome-element\" );","title":"elById(id: string): Element | null"},{"location":"javascript/helper-functions/#elbyselselector-string-context-element-element-null","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Select a single element based on a CSS selector, optionally limiting the results to be a direct or indirect children of the context element. var element = elBySel ( \".some-element\" ); // equals var element = document . querySelector ( \".some-element\" ); // limiting the scope to a context element: var element = elBySel ( \".some-element\" , context ); // equals var element = context . querySelector ( \".some-element\" );","title":"elBySel(selector: string, context?: Element): Element | null"},{"location":"javascript/helper-functions/#elbyselallselector-string-context-element-callback-element-element-void-nodelist","text":"The underlying querySelector() -method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on Element.querySelector() to learn more about this. Finds and returns a NodeList containing all elements that match the provided CSS selector. Although NodeList is an array-like structure, it is not possible to iterate over it using array functions, including .forEach() which is not available in Internet Explorer 11. var elements = elBySelAll ( \".some-element\" ); // equals var elements = document . querySelectorAll ( \".some-element\" ); // limiting the scope to a context element: var elements = elBySelAll ( \".some-element\" , context ); // equals var elements = context . querySelectorAll ( \".some-element\" );","title":"elBySelAll(selector: string, context?: Element, callback: (element: Element) =&gt; void): NodeList"},{"location":"javascript/helper-functions/#callback-to-iterate-over-elements","text":"elBySelAll() supports an optional third parameter that expects a callback function that is invoked for every element in the list. // set the 2nd parameter to `undefined` or `null` to query the whole document elBySelAll ( \".some-element\" , undefined , function ( element ) { // is called for each element }); // limiting the scope to a context element: elBySelAll ( \".some-element\" , context , function ( element ) { // is called for each element });","title":"Callback to Iterate Over Elements"},{"location":"javascript/helper-functions/#elclosestelement-element-selector-string-element-null","text":"Returns the first Element that matches the provided CSS selector, this will return the provided element itself if it matches the selector. var element = elClosest ( context , \".some-element\" ); // equals var element = context . closest ( \".some-element\" );","title":"elClosest(element: Element, selector: string): Element | null"},{"location":"javascript/helper-functions/#text-nodes","text":"If the provided context is a Text -node, the function will move the context to the parent element before applying the CSS selector. If the Text has no parent, null is returned without evaluating the selector.","title":"Text Nodes"},{"location":"javascript/helper-functions/#elbyclassclassname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements that match the provided CSS class now and in the future! The collection is automatically updated whenever an element with that class is added or removed from the DOM, it will also include elements that get dynamically assigned or removed this CSS class. You absolutely need to understand that this collection is dynamic, that means that elements can and will be added and removed from the collection even while you iterate over it. There are only very few cases where you would need such a collection, almost always elBySelAll() is what you're looking for. // no leading dot! var elements = elByClass ( \"some-element\" ); // equals var elements = document . getElementsByClassName ( \"some-element\" ); // limiting the scope to a context element: var elements = elByClass ( \"some-element\" , context ); // equals var elements = context . getElementsByClassName ( \".some-element\" );","title":"elByClass(className: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#elbytagtagname-string-context-element-nodelist","text":"Returns a live NodeList containing all elements with the provided tag name now and in the future! Please read the remarks on elByClass() above to understand the implications of this. var elements = elByTag ( \"div\" ); // equals var elements = document . getElementsByTagName ( \"div\" ); // limiting the scope to a context element: var elements = elByTag ( \"div\" , context ); // equals var elements = context . getElementsByTagName ( \"div\" );","title":"elByTag(tagName: string, context?: Element): NodeList"},{"location":"javascript/helper-functions/#utility-functions","text":"","title":"Utility Functions"},{"location":"javascript/helper-functions/#elinnererrorelement-element-errormessage-string-ishtml-boolean-element-null","text":"Unified function to display and remove inline error messages for input elements, please read the section in the migration docs to learn more about this function.","title":"`elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null``"},{"location":"javascript/helper-functions/#string-extensions","text":"","title":"String Extensions"},{"location":"javascript/helper-functions/#hashcode-string","text":"Computes a numeric hash value of a string similar to Java's String.hashCode() method. console . log ( \"Hello World\" . hashCode ()); // outputs: -862545276","title":"hashCode(): string"},{"location":"javascript/legacy-api/","text":"Legacy JavaScript API # Introduction # The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API. Classes # Singletons # Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; }); Regular Classes # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); Inheritance # // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); Ajax Requests # // App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; }); Phrases # < script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script > Event-Listener # < script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Legacy API"},{"location":"javascript/legacy-api/#legacy-javascript-api","text":"","title":"Legacy JavaScript API"},{"location":"javascript/legacy-api/#introduction","text":"The legacy JavaScript API is the original code that was part of the 2.x series of WoltLab Suite, formerly known as WoltLab Community Framework. It has been superseded for the most part by the ES5/AMD-modules API introduced with WoltLab Suite 3.0. Some parts still exist to this day for backwards-compatibility and because some less important components have not been rewritten yet. The old API is still supported, but marked as deprecated and will continue to be replaced parts by part in future releases, up until their entire removal, including jQuery support. This guide does not provide any explanation on the usage of those legacy components, but instead serves as a cheat sheet to convert code to use the new API.","title":"Introduction"},{"location":"javascript/legacy-api/#classes","text":"","title":"Classes"},{"location":"javascript/legacy-api/#singletons","text":"Singleton instances are designed to provide a unique \"instance\" of an object regardless of when its first instance was created. Due to the lack of a class construct in ES5, they are represented by mere objects that act as an instance. // App.js window . App = {}; App . Foo = { bar : function () {} }; // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; return { bar : function () {} }; });","title":"Singletons"},{"location":"javascript/legacy-api/#regular-classes","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; });","title":"Regular Classes"},{"location":"javascript/legacy-api/#inheritance","text":"// App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"Inheritance"},{"location":"javascript/legacy-api/#ajax-requests","text":"// App.js App . Foo = Class . extend ({ _proxy : null , init : function () { this . _proxy = new WCF . Action . Proxy ({ success : $ . proxy ( this . _success , this ) }); }, bar : function () { this . _proxy . setOption ( \"data\" , { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" , objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); this . _proxy . sendRequest (); }, _success : function ( data ) { // ajax request result } }); // --- NEW API --- // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {} Foo . prototype = { bar : function () { Ajax . api ( this , { objectIDs : [ 1 , 2 , 3 ], parameters : { foo : \"bar\" , baz : true } }); }, // magic method! _ajaxSuccess : function ( data ) { // ajax request result }, // magic method! _ajaxSetup : function () { return { actionName : \"baz\" , className : \"app\\\\foo\\\\FooAction\" } } } return Foo ; });","title":"Ajax Requests"},{"location":"javascript/legacy-api/#phrases","text":"< script data-relocate = \"true\" > $ ( function () { WCF . Language . addObject ({ 'app.foo.bar' : '{lang}app.foo.bar{/lang}' }); console . log ( WCF . Language . get ( \"app.foo.bar\" )); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); console . log ( Language . get ( \"app.foo.bar\" )); }); </ script >","title":"Phrases"},{"location":"javascript/legacy-api/#event-listener","text":"< script data-relocate = \"true\" > $ ( function () { WCF . System . Event . addListener ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked.\" ); }); WCF . System . Event . fireEvent ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script > <!-- NEW API --> < script data-relocate = \"true\" > require ([ \"EventHandler\" ], function ( EventHandler ) { EventHandler . add ( \"app.foo.bar\" , \"makeSnafucated\" , function ( data ) { console . log ( \"Event was invoked\" ); }); EventHandler . fire ( \"app.foo.bar\" , \"makeSnafucated\" , { some : \"data\" }); }); </ script >","title":"Event-Listener"},{"location":"javascript/new-api_ajax/","text":"Ajax Requests - JavaScript API # Ajax inside Modules # The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed. _ajaxSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; }); Request Settings # The object returned by the aforementioned _ajaxSetup() callback can contain these values: data # Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters . contentType # Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty. responseType # Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess ! type # Defaults to POST . The HTTP Verb used for this request. url # Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true. withCredentials # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint. autoAbort # Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code. ignoreError # Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback. silent # Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator. includeRequestedWith # Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint. failure # Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON. finalize # Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest . success # Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued. _ajaxSuccess() # This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests. _ajaxFailure() # Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed. Single Requests Without a Module # The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Ajax"},{"location":"javascript/new-api_ajax/#ajax-requests-javascript-api","text":"","title":"Ajax Requests - JavaScript API"},{"location":"javascript/new-api_ajax/#ajax-inside-modules","text":"The Ajax component was designed to be used from inside modules where an object reference is used to delegate request callbacks. This is acomplished through a set of magic methods that are automatically called when the request is created or its state has changed.","title":"Ajax inside Modules"},{"location":"javascript/new-api_ajax/#_ajaxsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _ajaxSetup() method to retrieve the basic configuration for this and any future requests. The data returned by _ajaxSetup() is cached and the data will be used to pre-populate the request data before sending it. The callee can overwrite any of these properties. It is intended to reduce the overhead when issuing request when these requests share the same properties, such as accessing the same endpoint. // App/Foo.js define ([ \"Ajax\" ], function ( Ajax ) { \"use strict\" ; function Foo () {}; Foo . prototype = { one : function () { // this will issue an ajax request with the parameter `value` set to `1` Ajax . api ( this ); }, two : function () { // this request is almost identical to the one issued with `.one()`, but // the value is now set to `2` for this invocation only. Ajax . api ( this , { parameters : { value : 2 } }); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 1 } } } } }; return Foo ; });","title":"_ajaxSetup()"},{"location":"javascript/new-api_ajax/#request-settings","text":"The object returned by the aforementioned _ajaxSetup() callback can contain these values:","title":"Request Settings"},{"location":"javascript/new-api_ajax/#data","text":"Defaults to {} . A plain JavaScript object that contains the request data that represents the form data of the request. The parameters key is recognized by the PHP Ajax API and becomes accessible through $this->parameters .","title":"data"},{"location":"javascript/new-api_ajax/#contenttype","text":"Defaults to application/x-www-form-urlencoded; charset=UTF-8 . The request content type, sets the Content-Type HTTP header if it is not empty.","title":"contentType"},{"location":"javascript/new-api_ajax/#responsetype","text":"Defaults to application/json . The server must respond with the Content-Type HTTP header set to this value, otherwise the request will be treated as failed. Requests for application/json will have the return body attempted to be evaluated as JSON. Other content types will only be validated based on the HTTP header, but no additional transformation is performed. For example, setting the responseType to application/xml will check the HTTP header, but will not transform the data parameter, you'll still receive a string in _ajaxSuccess !","title":"responseType"},{"location":"javascript/new-api_ajax/#type","text":"Defaults to POST . The HTTP Verb used for this request.","title":"type"},{"location":"javascript/new-api_ajax/#url","text":"Defaults to an empty string. Manual override for the request endpoint, it will be automatically set to the Core API endpoint if left empty. If the Core API endpoint is used, the options includeRequestedWith and withCredentials will be force-set to true.","title":"url"},{"location":"javascript/new-api_ajax/#withcredentials","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to false . Include cookies with this requested, is always true when url is (implicitly) set to the Core API endpoint.","title":"withCredentials"},{"location":"javascript/new-api_ajax/#autoabort","text":"Defaults to false . When set to true , any pending responses to earlier requests will be silently discarded when issuing a new request. This only makes sense if the new request is meant to completely replace the result of the previous one, regardless of its reponse body. Typical use-cases include input field with suggestions, where possible values are requested from the server, but the input changed faster than the server was able to reply. In this particular case the client is not interested in the result for an earlier value, auto-aborting these requests avoids implementing this logic in the requesting code.","title":"autoAbort"},{"location":"javascript/new-api_ajax/#ignoreerror","text":"Defaults to false . Any failing request will invoke the failure -callback to check if an error message should be displayed. Enabling this option will suppress the general error overlay that reports a failed request. You can achieve the same result by returning false in the failure -callback.","title":"ignoreError"},{"location":"javascript/new-api_ajax/#silent","text":"Defaults to false . Enabling this option will suppress the loading indicator overlay for this request, other non-\"silent\" requests will still trigger the loading indicator.","title":"silent"},{"location":"javascript/new-api_ajax/#includerequestedwith","text":"Enabling this parameter for any domain other than the current will trigger a CORS preflight request. Defaults to true . Sets the custom HTTP header X-Requested-With: XMLHttpRequest for the request, it is automatically set to true when url is pointing at the WSC API endpoint.","title":"includeRequestedWith"},{"location":"javascript/new-api_ajax/#failure","text":"Defaults to null . Optional callback function that will be invoked for requests that have failed for one of these reasons: 1. The request timed out. 2. The HTTP status is not 2xx or 304 . 3. A responseType was set, but the response HTTP header Content-Type did not match the expected value. 4. The responseType was set to application/json , but the response body was not valid JSON. The callback function receives the parameter xhr (the XMLHttpRequest object) and options (deep clone of the request parameters). If the callback returns false , the general error overlay for failed requests will be suppressed. There will be no error overlay if ignoreError is set to true or if the request failed while attempting to evaluate the response body as JSON.","title":"failure"},{"location":"javascript/new-api_ajax/#finalize","text":"Defaults to null . Optional callback function that will be invoked once the request has completed, regardless if it succeeded or failed. The only parameter it receives is options (the request parameters object), but it does not receive the request's XMLHttpRequest .","title":"finalize"},{"location":"javascript/new-api_ajax/#success","text":"Defaults to null . This semi-optional callback function will always be set to _ajaxSuccess() when invoking Ajax.api() . It receives four parameters: 1. data - The request's response body as a string, or a JavaScript object if contentType was set to application/json . 2. responseText - The unmodified response body, it equals the value for data for non-JSON requests. 3. xhr - The underlying XMLHttpRequest object. 4. requestData - The request parameters that were supplied when the request was issued.","title":"success"},{"location":"javascript/new-api_ajax/#_ajaxsuccess","text":"This callback method is automatically called for successful AJAX requests, it receives four parameters, with the first one containing either the response body as a string, or a JavaScript object for JSON requests.","title":"_ajaxSuccess()"},{"location":"javascript/new-api_ajax/#_ajaxfailure","text":"Optional callback function that is invoked for failed requests, it will be automatically called if the callee implements it, otherwise the global error handler will be executed.","title":"_ajaxFailure()"},{"location":"javascript/new-api_ajax/#single-requests-without-a-module","text":"The Ajax.api() method expects an object that is used to extract the request configuration as well as providing the callback functions when the request state changes. You can issue a simple Ajax request without object binding through Ajax.apiOnce() that will destroy the instance after the request was finalized. This method is significantly more expensive for repeated requests and does not offer deriving modules from altering the behavior. It is strongly recommended to always use Ajax.api() for requests to the WSC API endpoint. < script data-relocate = \"true\" > require ([ \"Ajax\" ], function ( Ajax ) { Ajax . apiOnce ({ data : { actionName : \"makeSnafucated\" , className : \"app\\\\data\\\\foo\\\\FooAction\" , parameters : { value : 3 } }, success : function ( data ) { elBySel ( \".some-element\" ). textContent = data . bar ; } }) }); </ script >","title":"Single Requests Without a Module"},{"location":"javascript/new-api_browser/","text":"Browser and Screen Sizes - JavaScript API # Ui/Screen # CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle. Supported Aliases # You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px) on(query: string, callbacks: Object): string # Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() . remove(query: string, uuid: string) # Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() . is(query: string): boolean # Tests if the provided media query currently matches and returns true on match. scrollDisable() # Temporarily prevents the page from being scrolled, until .scrollEnable() is called. scrollEnable() # Enables page scrolling again, unless another pending action has also prevented the page scrolling. Environment # The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks. browser(): string # Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default) platform(): string # Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"Browser and Screen Sizes"},{"location":"javascript/new-api_browser/#browser-and-screen-sizes-javascript-api","text":"","title":"Browser and Screen Sizes - JavaScript API"},{"location":"javascript/new-api_browser/#uiscreen","text":"CSS offers powerful media queries that alter the layout depending on the screen sizes, including but not limited to changes between landscape and portrait mode on mobile devices. The Ui/Screen module exposes a consistent interface to execute JavaScript code based on the same media queries that are available in the CSS code already. It features support for unmatching and executing code when a rule matches for the first time during the page lifecycle.","title":"Ui/Screen"},{"location":"javascript/new-api_browser/#supported-aliases","text":"You can pass in custom media queries, but it is strongly recommended to use the built-in media queries that match the same dimensions as your CSS. Alias Media Query screen-xs (max-width: 544px) screen-sm (min-width: 545px) and (max-width: 768px) screen-sm-down (max-width: 768px) screen-sm-up (min-width: 545px) screen-sm-md (min-width: 545px) and (max-width: 1024px) screen-md (min-width: 769px) and (max-width: 1024px) screen-md-down (max-width: 1024px) screen-md-up (min-width: 769px) screen-lg (min-width: 1025px)","title":"Supported Aliases"},{"location":"javascript/new-api_browser/#onquery-string-callbacks-object-string","text":"Registers a set of callback functions for the provided media query, the possible keys are match , unmatch and setup . The method returns a randomly generated UUIDv4 that is used to identify these callbacks and allows them to be removed via .remove() .","title":"on(query: string, callbacks: Object): string"},{"location":"javascript/new-api_browser/#removequery-string-uuid-string","text":"Removes all callbacks for a media query that match the UUIDv4 that was previously obtained from the call to .on() .","title":"remove(query: string, uuid: string)"},{"location":"javascript/new-api_browser/#isquery-string-boolean","text":"Tests if the provided media query currently matches and returns true on match.","title":"is(query: string): boolean"},{"location":"javascript/new-api_browser/#scrolldisable","text":"Temporarily prevents the page from being scrolled, until .scrollEnable() is called.","title":"scrollDisable()"},{"location":"javascript/new-api_browser/#scrollenable","text":"Enables page scrolling again, unless another pending action has also prevented the page scrolling.","title":"scrollEnable()"},{"location":"javascript/new-api_browser/#environment","text":"The Environment module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible! Sometimes it may be necessary to alter the behavior of your code depending on the browser platform (e. g. mobile devices) or based on a specific browser in order to work-around some quirks.","title":"Environment"},{"location":"javascript/new-api_browser/#browser-string","text":"Attempts to detect browsers based on their technology and supported CSS vendor prefixes, and although somewhat reliable for major browsers, it is highly recommended to use feature detection instead. Possible values: - chrome (includes Opera 15+ and Vivaldi) - firefox - safari - microsoft (Internet Explorer and Edge) - other (default)","title":"browser(): string"},{"location":"javascript/new-api_browser/#platform-string","text":"Attempts to detect the browser platform using user agent sniffing. Possible values: - ios - android - windows (IE Mobile) - mobile (generic mobile device) - desktop (default)","title":"platform(): string"},{"location":"javascript/new-api_core/","text":"Core Modules and Functions - JavaScript API # A brief overview of common methods that may be useful when writing any module. Core # clone(object: Object): Object # Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true }); extend(base: Object, ...merge: Object[]): Object # Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false }); inherit(base: Object, target: Object, merge?: Object) # Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; }); isPlainObject(object: Object): boolean # Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false }); triggerEvent(element: Element, eventName: string) # Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); }); Language # add(key: string, value: string) # Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script > addObject(object: Object) # Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script > get(key: string, parameters?: Object): string # Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); }); StringUtil # escapeHTML(str: string): string # Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt; escapeRegExp(str: string): string # Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions. lcfirst(str: string): string # Makes a string's first character lowercase. ucfirst(str: string): string # Makes a string's first character uppercase. unescapeHTML(str: string): string # Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"Core Functions"},{"location":"javascript/new-api_core/#core-modules-and-functions-javascript-api","text":"A brief overview of common methods that may be useful when writing any module.","title":"Core Modules and Functions - JavaScript API"},{"location":"javascript/new-api_core/#core","text":"","title":"Core"},{"location":"javascript/new-api_core/#cloneobject-object-object","text":"Creates a deep-clone of the provided object by value, removing any references on the original element, including arrays. However, this does not clone references to non-plain objects, these instances will be copied by reference. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 1 }; var obj2 = Core . clone ( obj1 ); console . log ( obj1 === obj2 ); // output: false console . log ( obj2 . hasOwnProperty ( \"a\" ) && obj2 . a === 1 ); // output: true });","title":"clone(object: Object): Object"},{"location":"javascript/new-api_core/#extendbase-object-merge-object-object","text":"Accepts an infinite amount of plain objects as parameters, values will be copied from the 2nd...nth object into the first object. The first parameter will be cloned and the resulting object is returned. require ([ \"Core\" ], function ( Core ) { var obj1 = { a : 2 }; var obj2 = { a : 1 , b : 2 }; var obj = Core . extend ({ b : 1 }, obj1 , obj2 ); console . log ( obj . b === 2 ); // output: true console . log ( obj . hasOwnProperty ( \"a\" ) && obj . a === 2 ); // output: false });","title":"extend(base: Object, ...merge: Object[]): Object"},{"location":"javascript/new-api_core/#inheritbase-object-target-object-merge-object","text":"Derives the second object's prototype from the first object, afterwards the derived class will pass the instanceof check against the original class. // App.js window . App = {}; App . Foo = Class . extend ({ bar : function () {} }); App . Baz = App . Foo . extend ({ makeSnafucated : function () {} }); // --- NEW API --- // App/Foo.js define ([], function () { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () {} }; return Foo ; }); // App/Baz.js define ([ \"Core\" , \"./Foo\" ], function ( Core , Foo ) { \"use strict\" ; function Baz () {}; Core . inherit ( Baz , Foo , { makeSnafucated : function () {} }); return Baz ; });","title":"inherit(base: Object, target: Object, merge?: Object)"},{"location":"javascript/new-api_core/#isplainobjectobject-object-boolean","text":"Verifies if an object is a plain JavaScript object and not an object instance. require ([ \"Core\" ], function ( Core ) { function Foo () {} Foo . prototype = { hello : \"world\" ; }; var obj1 = { hello : \"world\" }; var obj2 = new Foo (); console . log ( Core . isPlainObject ( obj1 )); // output: true console . log ( obj1 . hello === obj2 . hello ); // output: true console . log ( Core . isPlainObject ( obj2 )); // output: false });","title":"isPlainObject(object: Object): boolean"},{"location":"javascript/new-api_core/#triggereventelement-element-eventname-string","text":"Creates and dispatches a synthetic JavaScript event on an element. require ([ \"Core\" ], function ( Core ) { var element = elBySel ( \".some-element\" ); Core . triggerEvent ( element , \"click\" ); });","title":"triggerEvent(element: Element, eventName: string)"},{"location":"javascript/new-api_core/#language","text":"","title":"Language"},{"location":"javascript/new-api_core/#addkey-string-value-string","text":"Registers a new phrase. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . add ( 'app.foo.bar' , '{jslang}app.foo.bar{/jslang}' ); }); </ script >","title":"add(key: string, value: string)"},{"location":"javascript/new-api_core/#addobjectobject-object","text":"Registers a list of phrases using a plain object. < script data-relocate = \"true\" > require ([ \"Language\" ], function ( Language ) { Language . addObject ({ 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' }); }); </ script >","title":"addObject(object: Object)"},{"location":"javascript/new-api_core/#getkey-string-parameters-object-string","text":"Retrieves a phrase by its key, optionally supporting basic template scripting with dynamic variables passed using the parameters object. require ([ \"Language\" ], function ( Language ) { var title = Language . get ( \"app.foo.title\" ); var content = Language . get ( \"app.foo.content\" , { some : \"value\" }); });","title":"get(key: string, parameters?: Object): string"},{"location":"javascript/new-api_core/#stringutil","text":"","title":"StringUtil"},{"location":"javascript/new-api_core/#escapehtmlstr-string-string","text":"Escapes special HTML characters by converting them into an HTML entity. Character Replacement & &amp; \" &quot; < &lt; > &gt;","title":"escapeHTML(str: string): string"},{"location":"javascript/new-api_core/#escaperegexpstr-string-string","text":"Escapes a list of characters that have a special meaning in regular expressions and could alter the behavior when embedded into regular expressions.","title":"escapeRegExp(str: string): string"},{"location":"javascript/new-api_core/#lcfirststr-string-string","text":"Makes a string's first character lowercase.","title":"lcfirst(str: string): string"},{"location":"javascript/new-api_core/#ucfirststr-string-string","text":"Makes a string's first character uppercase.","title":"ucfirst(str: string): string"},{"location":"javascript/new-api_core/#unescapehtmlstr-string-string","text":"Converts some HTML entities into their original character. This is the reverse function of escapeHTML() .","title":"unescapeHTML(str: string): string"},{"location":"javascript/new-api_data-structures/","text":"Data Structures - JavaScript API # Introduction # JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11. Dictionary # Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object. set(key: string, value: any) # Adds or updates an item using the provided key. Numeric keys will be converted into strings. delete(key: string) # Removes an item from the collection. has(key: string): boolean # Returns true if the key is contained in the collection. get(key: string): any # Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence. forEach(callback: (value: any, key: string) => void) # Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key. size: number # This read-only property counts the number of items in the collection. List # Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array. add(value: any) # Adds a value to the list. If the value is already part of the list, this method will silently abort. clear() # Resets the collection. delete(value: any): boolean # Attempts to remove a value from the list, it returns true if the value has been part of the list. forEach(callback: (value: any) => void) # Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value. has(value: any): boolean # Returns true if the provided value is part of this list. size: number # This read-only property counts the number of items in the list. ObjectMap # This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects. set(key: Object, value: Object) # Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten. delete(key: Object) # Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection. has(key: Object): boolean # Returns true if there is a value for the provided key in this collection. get(key: Object): Object | undefined # Retrieves the value of the provided key, or undefined if the key was not found.","title":"Data Structures"},{"location":"javascript/new-api_data-structures/#data-structures-javascript-api","text":"","title":"Data Structures - JavaScript API"},{"location":"javascript/new-api_data-structures/#introduction","text":"JavaScript offers only limited types of collections to hold and iterate over data. Despite the ongoing efforts in ES6 and newer, these new data structures and access methods, such as for \u2026 of , are not available in the still supported Internet Explorer 11.","title":"Introduction"},{"location":"javascript/new-api_data-structures/#dictionary","text":"Represents a simple key-value map, but unlike the use of plain objects, will always to guarantee to iterate over directly set values only. In supported browsers this will use a native Map internally, otherwise a plain object.","title":"Dictionary"},{"location":"javascript/new-api_data-structures/#setkey-string-value-any","text":"Adds or updates an item using the provided key. Numeric keys will be converted into strings.","title":"set(key: string, value: any)"},{"location":"javascript/new-api_data-structures/#deletekey-string","text":"Removes an item from the collection.","title":"delete(key: string)"},{"location":"javascript/new-api_data-structures/#haskey-string-boolean","text":"Returns true if the key is contained in the collection.","title":"has(key: string): boolean"},{"location":"javascript/new-api_data-structures/#getkey-string-any","text":"Returns the value for the provided key, or undefined if the key was not found. Use .has() to check for key existence.","title":"get(key: string): any"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-key-string-void","text":"Iterates over all items in the collection in an arbitrary order and invokes the supplied callback with the value and the key.","title":"forEach(callback: (value: any, key: string) =&gt; void)"},{"location":"javascript/new-api_data-structures/#size-number","text":"This read-only property counts the number of items in the collection.","title":"size: number"},{"location":"javascript/new-api_data-structures/#list","text":"Represents a list of unique values. In supported browsers this will use a native Set internally, otherwise an array.","title":"List"},{"location":"javascript/new-api_data-structures/#addvalue-any","text":"Adds a value to the list. If the value is already part of the list, this method will silently abort.","title":"add(value: any)"},{"location":"javascript/new-api_data-structures/#clear","text":"Resets the collection.","title":"clear()"},{"location":"javascript/new-api_data-structures/#deletevalue-any-boolean","text":"Attempts to remove a value from the list, it returns true if the value has been part of the list.","title":"delete(value: any): boolean"},{"location":"javascript/new-api_data-structures/#foreachcallback-value-any-void","text":"Iterates over all values in the list in an arbitrary order and invokes the supplied callback for each value.","title":"forEach(callback: (value: any) =&gt; void)"},{"location":"javascript/new-api_data-structures/#hasvalue-any-boolean","text":"Returns true if the provided value is part of this list.","title":"has(value: any): boolean"},{"location":"javascript/new-api_data-structures/#size-number_1","text":"This read-only property counts the number of items in the list.","title":"size: number"},{"location":"javascript/new-api_data-structures/#objectmap","text":"This class uses a WeakMap internally, the keys are only weakly referenced and do not prevent garbage collection. Represents a collection where any kind of objects, such as class instances or DOM elements, can be used as key. These keys are weakly referenced and will not prevent garbage collection from happening, but this also means that it is not possible to enumerate or iterate over the stored keys and values. This class is especially useful when you want to store additional data for objects that may get disposed on runtime, such as DOM elements. Using any regular data collections will cause the object to be referenced indefinitely, preventing the garbage collection from removing orphaned objects.","title":"ObjectMap"},{"location":"javascript/new-api_data-structures/#setkey-object-value-object","text":"Adds the key with the provided value to the map, if the key was already part of the collection, its value is overwritten.","title":"set(key: Object, value: Object)"},{"location":"javascript/new-api_data-structures/#deletekey-object","text":"Attempts to remove a key from the collection. The method will abort silently if the key is not part of the collection.","title":"delete(key: Object)"},{"location":"javascript/new-api_data-structures/#haskey-object-boolean","text":"Returns true if there is a value for the provided key in this collection.","title":"has(key: Object): boolean"},{"location":"javascript/new-api_data-structures/#getkey-object-object-undefined","text":"Retrieves the value of the provided key, or undefined if the key was not found.","title":"get(key: Object): Object | undefined"},{"location":"javascript/new-api_dialogs/","text":"Dialogs - JavaScript API # Introduction # Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs. _dialogSetup() # The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; }); id: string # The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id. source: any # There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog. options: Object # All configuration options and callbacks are handled through this object. options.backdropCloseOnClick: boolean # Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false . options.closable: boolean # Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only. options.closeButtonLabel: string # Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button. options.closeConfirmMessage: string # Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user. options.title: string # Defaults to \"\" . The phrase that is displayed in the dialog title. options.onBeforeClose: (id: string) => void # Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented. options.onClose: (id: string) => void # Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point. options.onShow: (content: Element) => void # Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet. setTitle(id: string | Object, title: string) # Sets the title of a dialog. setCallback(id: string | Object, key: string, value: (data: any) => void | null) # Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow . rebuild(id: string | Object) # Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback. close(id: string | Object) # Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation. getDialog(id: string | Object): Object # This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element. isOpen(id: string | Object): boolean # Returns true if the dialog exists and is open.","title":"Dialogs"},{"location":"javascript/new-api_dialogs/#dialogs-javascript-api","text":"","title":"Dialogs - JavaScript API"},{"location":"javascript/new-api_dialogs/#introduction","text":"Dialogs are full screen overlays that cover the currently visible window area using a semi-opague backdrop and a prominently placed dialog window in the foreground. They shift the attention away from the original content towards the dialog and usually contain additional details and/or dedicated form inputs.","title":"Introduction"},{"location":"javascript/new-api_dialogs/#_dialogsetup","text":"The lazy initialization is performed upon the first invocation from the callee, using the magic _dialogSetup() method to retrieve the basic configuration for the dialog construction and any event callbacks. // App/Foo.js define ([ \"Ui/Dialog\" ], function ( UiDialog ) { \"use strict\" ; function Foo () {}; Foo . prototype = { bar : function () { // this will open the dialog constructed by _dialogSetup UiDialog . open ( this ); }, _dialogSetup : function () { return { id : \"myDialog\" , source : \"<p>Hello World!</p>\" , options : { onClose : function () { // the fancy dialog was closed! } } } } }; return Foo ; });","title":"_dialogSetup()"},{"location":"javascript/new-api_dialogs/#id-string","text":"The id is used to identify a dialog on runtime, but is also part of the first- time setup when the dialog has not been opened before. If source is undefined , the module attempts to construct the dialog using an element with the same id.","title":"id: string"},{"location":"javascript/new-api_dialogs/#source-any","text":"There are six different types of value that source does allow and each of them changes how the initial dialog is constructed: undefined The dialog exists already and the value of id should be used to identify the element. null The HTML is provided using the second argument of .open() . () => void If the source is a function, it is executed and is expected to start the dialog initialization itself. Object Plain objects are interpreted as parameters for an Ajax request, in particular source.data will be used to issue the request. It is possible to specify the key source.after as a callback (content: Element, responseData: Object) => void that is executed after the dialog was opened. string The string is expected to be plain HTML that should be used to construct the dialog. DocumentFragment A new container <div> with the provided id is created and the contents of the DocumentFragment is appended to it. This container is then used for the dialog.","title":"source: any"},{"location":"javascript/new-api_dialogs/#options-object","text":"All configuration options and callbacks are handled through this object.","title":"options: Object"},{"location":"javascript/new-api_dialogs/#optionsbackdropcloseonclick-boolean","text":"Defaults to true . Clicks on the dialog backdrop will close the top-most dialog. This option will be force-disabled if the option closeable is set to false .","title":"options.backdropCloseOnClick: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosable-boolean","text":"Defaults to true . Enables the close button in the dialog title, when disabled the dialog can be closed through the .close() API call only.","title":"options.closable: boolean"},{"location":"javascript/new-api_dialogs/#optionsclosebuttonlabel-string","text":"Defaults to Language.get(\"wcf.global.button.close\") . The phrase that is displayed in the tooltip for the close button.","title":"options.closeButtonLabel: string"},{"location":"javascript/new-api_dialogs/#optionscloseconfirmmessage-string","text":"Defaults to \"\" . Shows a confirmation dialog using the configured message before closing the dialog. The dialog will not be closed if the dialog is rejected by the user.","title":"options.closeConfirmMessage: string"},{"location":"javascript/new-api_dialogs/#optionstitle-string","text":"Defaults to \"\" . The phrase that is displayed in the dialog title.","title":"options.title: string"},{"location":"javascript/new-api_dialogs/#optionsonbeforeclose-id-string-void","text":"Defaults to null . The callback is executed when the user clicks on the close button or, if enabled, on the backdrop. The callback is responsible to close the dialog by itself, the default close behavior is automatically prevented.","title":"options.onBeforeClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonclose-id-string-void","text":"Defaults to null . The callback is notified once the dialog is about to be closed, but is still visible at this point. It is not possible to abort the close operation at this point.","title":"options.onClose: (id: string) =&gt; void"},{"location":"javascript/new-api_dialogs/#optionsonshow-content-element-void","text":"Defaults to null . Receives the dialog content element as its only argument, allowing the callback to modify the DOM or to register event listeners before the dialog is presented to the user. The dialog is already visible at call time, but the dialog has not been finalized yet.","title":"options.onShow: (content: Element) =&gt; void"},{"location":"javascript/new-api_dialogs/#settitleid-string-object-title-string","text":"Sets the title of a dialog.","title":"setTitle(id: string | Object, title: string)"},{"location":"javascript/new-api_dialogs/#setcallbackid-string-object-key-string-value-data-any-void-null","text":"Sets a callback function after the dialog initialization, the special value null will remove a previously set callback. Valid values for key are onBeforeClose , onClose and onShow .","title":"setCallback(id: string | Object, key: string, value: (data: any) =&gt; void | null)"},{"location":"javascript/new-api_dialogs/#rebuildid-string-object","text":"Rebuilds a dialog by performing various calculations on the maximum dialog height in regards to the overflow handling and adjustments for embedded forms. This method is automatically invoked whenever a dialog is shown, after invoking the options.onShow callback.","title":"rebuild(id: string | Object)"},{"location":"javascript/new-api_dialogs/#closeid-string-object","text":"Closes an open dialog, this will neither trigger a confirmation dialog, nor does it invoke the options.onBeforeClose callback. The options.onClose callback will always be invoked, but it cannot abort the close operation.","title":"close(id: string | Object)"},{"location":"javascript/new-api_dialogs/#getdialogid-string-object-object","text":"This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the .set*() methods only. Returns the internal dialog data that is attached to a dialog. The most important key is .content which holds a reference to the dialog's inner content element.","title":"getDialog(id: string | Object): Object"},{"location":"javascript/new-api_dialogs/#isopenid-string-object-boolean","text":"Returns true if the dialog exists and is open.","title":"isOpen(id: string | Object): boolean"},{"location":"javascript/new-api_dom/","text":"Working with the DOM - JavaScript API # Helper Functions # There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports. Dom/Util # createFragmentFromHtml(html: string): DocumentFragment # Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes. identify(element: Element): string # Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead. outerHeight(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom . outerWidth(element: Element, styles?: CSSStyleDeclaration): number # Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right . outerDimensions(element: Element): { height: number, width: number } # Computes the outer dimensions of an element including its margins. offset(element: Element): { top: number, left: number } # Computes the element's offset relative to the top left corner of the document. setInnerHtml(element: Element, innerHtml: string) # Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance. contains(element: Element, child: Element): boolean # Evaluates if element is a direct or indirect parent element of child . unwrapChildNodes(element: Element) # Moves all child nodes out of element while maintaining their order, then removes element from the document. Dom/ChangeListener # This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"DOM"},{"location":"javascript/new-api_dom/#working-with-the-dom-javascript-api","text":"","title":"Working with the DOM - JavaScript API"},{"location":"javascript/new-api_dom/#helper-functions","text":"There is large set of helper functions that assist you when working with the DOM tree and its elements. These functions are globally available and do not require explicit module imports.","title":"Helper Functions"},{"location":"javascript/new-api_dom/#domutil","text":"","title":"Dom/Util"},{"location":"javascript/new-api_dom/#createfragmentfromhtmlhtml-string-documentfragment","text":"Parses a HTML string and creates a DocumentFragment object that holds the resulting nodes.","title":"createFragmentFromHtml(html: string): DocumentFragment"},{"location":"javascript/new-api_dom/#identifyelement-element-string","text":"Retrieves the unique identifier ( id ) of an element. If it does not currently have an id assigned, a generic identifier is used instead.","title":"identify(element: Element): string"},{"location":"javascript/new-api_dom/#outerheightelement-element-styles-cssstyledeclaration-number","text":"Computes the outer height of an element using the element's offsetHeight and the sum of the rounded down values for margin-top and margin-bottom .","title":"outerHeight(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerwidthelement-element-styles-cssstyledeclaration-number","text":"Computes the outer width of an element using the element's offsetWidth and the sum of the rounded down values for margin-left and margin-right .","title":"outerWidth(element: Element, styles?: CSSStyleDeclaration): number"},{"location":"javascript/new-api_dom/#outerdimensionselement-element-height-number-width-number","text":"Computes the outer dimensions of an element including its margins.","title":"outerDimensions(element: Element): { height: number, width: number }"},{"location":"javascript/new-api_dom/#offsetelement-element-top-number-left-number","text":"Computes the element's offset relative to the top left corner of the document.","title":"offset(element: Element): { top: number, left: number }"},{"location":"javascript/new-api_dom/#setinnerhtmlelement-element-innerhtml-string","text":"Sets the inner HTML of an element via element.innerHTML = innerHtml . Browsers do not evaluate any embedded <script> tags, therefore this method extracts each of them, creates new <script> tags and inserts them in their original order of appearance.","title":"setInnerHtml(element: Element, innerHtml: string)"},{"location":"javascript/new-api_dom/#containselement-element-child-element-boolean","text":"Evaluates if element is a direct or indirect parent element of child .","title":"contains(element: Element, child: Element): boolean"},{"location":"javascript/new-api_dom/#unwrapchildnodeselement-element","text":"Moves all child nodes out of element while maintaining their order, then removes element from the document.","title":"unwrapChildNodes(element: Element)"},{"location":"javascript/new-api_dom/#domchangelistener","text":"This class is used to observe specific changes to the DOM, for example after an Ajax request has completed. For performance reasons this is a manually-invoked listener that does not rely on a MutationObserver . require ([ \"Dom/ChangeListener\" ], function ( DomChangeListener ) { DomChangeListener . add ( \"App/Foo\" , function () { // the DOM may have been altered significantly }); // propagate changes to the DOM DomChangeListener . trigger (); });","title":"Dom/ChangeListener"},{"location":"javascript/new-api_events/","text":"Event Handling - JavaScript API # EventKey # This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); }); ArrowDown(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2193 key. ArrowLeft(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2190 key. ArrowRight(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2192 key. ArrowUp(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u2191 key. Comma(event: KeyboardEvent): boolean # Returns true if the user has pressed the , key. Enter(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b2 key. Escape(event: KeyboardEvent): boolean # Returns true if the user has pressed the Esc key. Tab(event: KeyboardEvent): boolean # Returns true if the user has pressed the \u21b9 key. EventHandler # A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time. Identifiying Events with the Developer Tools # The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo add(identifier: string, action: string, callback: (data: Object) => void): string # Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method. fire(identifier: string, action: string, data?: Object) # Triggers an event using an optional data object that is passed to each listener by reference. remove(identifier: string, action: string, uuid: string) # Removes a previously registered event listener using the UUID returned by add() . removeAll(identifier: string, action: string) # Removes all event listeners registered for the provided identifier and action . removeAllBySuffix(identifier: string, suffix: string) # Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"Event Handling"},{"location":"javascript/new-api_events/#event-handling-javascript-api","text":"","title":"Event Handling - JavaScript API"},{"location":"javascript/new-api_events/#eventkey","text":"This class offers a set of static methods that can be used to determine if some common keys are being pressed. Internally it compares either the .key property if it is supported or the value of .which . require ([ \"EventKey\" ], function ( EventKey ) { elBySel ( \".some-input\" ). addEventListener ( \"keydown\" , function ( event ) { if ( EventKey . Enter ( event )) { // the `Enter` key was pressed } }); });","title":"EventKey"},{"location":"javascript/new-api_events/#arrowdownevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2193 key.","title":"ArrowDown(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowleftevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2190 key.","title":"ArrowLeft(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowrightevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2192 key.","title":"ArrowRight(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#arrowupevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u2191 key.","title":"ArrowUp(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#commaevent-keyboardevent-boolean","text":"Returns true if the user has pressed the , key.","title":"Comma(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#enterevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b2 key.","title":"Enter(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#escapeevent-keyboardevent-boolean","text":"Returns true if the user has pressed the Esc key.","title":"Escape(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#tabevent-keyboardevent-boolean","text":"Returns true if the user has pressed the \u21b9 key.","title":"Tab(event: KeyboardEvent): boolean"},{"location":"javascript/new-api_events/#eventhandler","text":"A synchronous event system based on string identifiers rather than DOM elements, similar to the PHP event system in WoltLab Suite. Any components can listen to events or trigger events itself at any time.","title":"EventHandler"},{"location":"javascript/new-api_events/#identifiying-events-with-the-developer-tools","text":"The Developer Tools in WoltLab Suite 3.1 offer an easy option to identify existing events that are fired while code is being executed. You can enable this watch mode through your browser's console using Devtools.toggleEventLogging() : > Devtools.toggleEventLogging(); < Event logging enabled < [Devtools.EventLogging] Firing event: bar @ com.example.app.foo < [Devtools.EventLogging] Firing event: baz @ com.example.app.foo","title":"Identifiying Events with the Developer Tools"},{"location":"javascript/new-api_events/#addidentifier-string-action-string-callback-data-object-void-string","text":"Adding an event listeners returns a randomly generated UUIDv4 that is used to identify the listener. This UUID is required to remove a specific listener through the remove() method.","title":"add(identifier: string, action: string, callback: (data: Object) =&gt; void): string"},{"location":"javascript/new-api_events/#fireidentifier-string-action-string-data-object","text":"Triggers an event using an optional data object that is passed to each listener by reference.","title":"fire(identifier: string, action: string, data?: Object)"},{"location":"javascript/new-api_events/#removeidentifier-string-action-string-uuid-string","text":"Removes a previously registered event listener using the UUID returned by add() .","title":"remove(identifier: string, action: string, uuid: string)"},{"location":"javascript/new-api_events/#removeallidentifier-string-action-string","text":"Removes all event listeners registered for the provided identifier and action .","title":"removeAll(identifier: string, action: string)"},{"location":"javascript/new-api_events/#removeallbysuffixidentifier-string-suffix-string","text":"Removes all event listeners for an identifier whose action ends with the value of suffix .","title":"removeAllBySuffix(identifier: string, suffix: string)"},{"location":"javascript/new-api_ui/","text":"User Interface - JavaScript API # Ui/Alignment # Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements. set(element: Element, referenceElement: Element, options: Object) # Calculates and sets the alignment of the element element . verticalOffset: number # Defaults to 0 . Creates a gap between the element and the reference element, in pixels. pointer: boolean # Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer . pointerOffset: number # Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center. pointerClassNames: string[] # Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element. refDimensionsElement: Element # Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions. horizontal: string # This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used. vertical: string # Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used. allowFlip: string # The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints. Ui/CloseOverlay # Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); }); add(identifier: string, callback: () => void) # Adds a callback that will be invoked when the user clicks anywhere else. Ui/Confirmation # Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); }); show(options: Object) # Displays a dialog overlay with actions buttons to confirm or reject the dialog. cancel: (parameters: Object) => void # Defaults to null . Callback that is invoked when the dialog was rejected. confirm: (parameters: Object) => void # Defaults to null . Callback that is invoked when the user has confirmed the dialog. message: string # Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled. messageIsHtml # Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML. parameters: Object # Optional list of parameter options that will be passed to the cancel() and confirm() callbacks. template: string # An optional HTML template that will be inserted into the dialog content area, but after the message section. Ui/Notification # Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); }); show(message: string, callback?: () => void, cssClassName?: string) # Shows the notification and executes the callback after 2 seconds.","title":"User Interface"},{"location":"javascript/new-api_ui/#user-interface-javascript-api","text":"","title":"User Interface - JavaScript API"},{"location":"javascript/new-api_ui/#uialignment","text":"Calculates the alignment of one element relative to another element, with support for boundary constraints, alignment restrictions and additional pointer elements.","title":"Ui/Alignment"},{"location":"javascript/new-api_ui/#setelement-element-referenceelement-element-options-object","text":"Calculates and sets the alignment of the element element .","title":"set(element: Element, referenceElement: Element, options: Object)"},{"location":"javascript/new-api_ui/#verticaloffset-number","text":"Defaults to 0 . Creates a gap between the element and the reference element, in pixels.","title":"verticalOffset: number"},{"location":"javascript/new-api_ui/#pointer-boolean","text":"Defaults to false . Sets the position of the pointer element, requires an existing child of the element with the CSS class .elementPointer .","title":"pointer: boolean"},{"location":"javascript/new-api_ui/#pointeroffset-number","text":"Defaults to 4 . The margin from the left/right edge of the element and is used to avoid the arrow from being placed right at the edge. Does not apply when aligning the element to the reference elemnent's center.","title":"pointerOffset: number"},{"location":"javascript/new-api_ui/#pointerclassnames-string","text":"Defaults to [] . If your element uses CSS-only pointers, such as using the ::before or ::after pseudo selectors, you can specifiy two separate CSS class names that control the alignment: pointerClassNames[0] is applied to the element when the pointer is displayed at the bottom. pointerClassNames[1] is used to align the pointer to the right side of the element.","title":"pointerClassNames: string[]"},{"location":"javascript/new-api_ui/#refdimensionselement-element","text":"Defaults to null . An alternative element that will be used to determine the position and dimensions of the reference element. This can be useful if you reference element is contained in a wrapper element with alternating dimensions.","title":"refDimensionsElement: Element"},{"location":"javascript/new-api_ui/#horizontal-string","text":"This value is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. Defaults to \"left\" . Sets the prefered alignment, accepts either left or right . The value left instructs the module to align the element with the left boundary of the reference element. The horizontal alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of horizontal is used.","title":"horizontal: string"},{"location":"javascript/new-api_ui/#vertical-string","text":"Defaults to \"bottom\" . Sets the prefered alignment, accepts either bottom or top . The value bottom instructs the module to align the element below the reference element. The vertical alignment is used as the default and a flip only occurs, if there is not enough space in the desired direction. If the element exceeds the boundaries in both directions, the value of vertical is used.","title":"vertical: string"},{"location":"javascript/new-api_ui/#allowflip-string","text":"The value for horizontal is automatically flipped for RTL (right-to-left) languages, left is changed into right and vice versa. This setting only controls the behavior when violating space constraints, therefore the aforementioned transformation is always applied. Defaults to \"both\" . Restricts the automatic alignment flipping if the element exceeds the window boundaries in the instructed direction. both - No restrictions. horizontal - Element can be aligned with the left or the right boundary of the reference element, but the vertical position is fixed. vertical - Element can be aligned below or above the reference element, but the vertical position is fixed. none - No flipping can occur, the element will be aligned regardless of any space constraints.","title":"allowFlip: string"},{"location":"javascript/new-api_ui/#uicloseoverlay","text":"Register elements that should be closed when the user clicks anywhere else, such as drop-down menus or tooltips. require ([ \"Ui/CloseOverlay\" ], function ( UiCloseOverlay ) { UiCloseOverlay . add ( \"App/Foo\" , function () { // invoked, close something }); });","title":"Ui/CloseOverlay"},{"location":"javascript/new-api_ui/#addidentifier-string-callback-void","text":"Adds a callback that will be invoked when the user clicks anywhere else.","title":"add(identifier: string, callback: () =&gt; void)"},{"location":"javascript/new-api_ui/#uiconfirmation","text":"Prompt the user to make a decision before carrying out an action, such as a safety warning before permanently deleting content. require ([ \"Ui/Confirmation\" ], function ( UiConfirmation ) { UiConfirmation . show ({ confirm : function () { // the user has confirmed the dialog }, message : \"Do you really want to continue?\" }); });","title":"Ui/Confirmation"},{"location":"javascript/new-api_ui/#showoptions-object","text":"Displays a dialog overlay with actions buttons to confirm or reject the dialog.","title":"show(options: Object)"},{"location":"javascript/new-api_ui/#cancel-parameters-object-void","text":"Defaults to null . Callback that is invoked when the dialog was rejected.","title":"cancel: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#confirm-parameters-object-void","text":"Defaults to null . Callback that is invoked when the user has confirmed the dialog.","title":"confirm: (parameters: Object) =&gt; void"},{"location":"javascript/new-api_ui/#message-string","text":"Defaults to '\"\"'. Text that is displayed in the content area of the dialog, optionally this can be HTML, but this requires messageIsHtml to be enabled.","title":"message: string"},{"location":"javascript/new-api_ui/#messageishtml","text":"Defaults to false . The message option is interpreted as text-only, setting this option to true will cause the message to be evaluated as HTML.","title":"messageIsHtml"},{"location":"javascript/new-api_ui/#parameters-object","text":"Optional list of parameter options that will be passed to the cancel() and confirm() callbacks.","title":"parameters: Object"},{"location":"javascript/new-api_ui/#template-string","text":"An optional HTML template that will be inserted into the dialog content area, but after the message section.","title":"template: string"},{"location":"javascript/new-api_ui/#uinotification","text":"Displays a simple notification at the very top of the window, such as a success message for Ajax based actions. require ([ \"Ui/Notification\" ], function ( UiNotification ) { UiNotification . show ( \"Your changes have been saved.\" , function () { // this callback will be invoked after 2 seconds }, \"success\" ); });","title":"Ui/Notification"},{"location":"javascript/new-api_ui/#showmessage-string-callback-void-cssclassname-string","text":"Shows the notification and executes the callback after 2 seconds.","title":"show(message: string, callback?: () =&gt; void, cssClassName?: string)"},{"location":"javascript/new-api_writing-a-module/","text":"Writing a Module - JavaScript API # Introduction # The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start. Defining a Module # The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; }); Loading a Module # Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script > Module Aliases # Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Writing a module"},{"location":"javascript/new-api_writing-a-module/#writing-a-module-javascript-api","text":"","title":"Writing a Module - JavaScript API"},{"location":"javascript/new-api_writing-a-module/#introduction","text":"The new JavaScript-API was introduced with WoltLab Suite 3.0 and was a major change in all regards. The previously used API heavily relied on larger JavaScript files that contained a lot of different components with hidden dependencies and suffered from extensive jQuery usage for historic reasons. Eventually a new API was designed that solves the issues with the legacy API by following a few basic principles: 1. Vanilla ES5-JavaScript. It allows us to achieve the best performance across all platforms, there is simply no reason to use jQuery today and the performance penalty on mobile devices is a real issue. 2. Strict usage of modules. Each component is placed in an own file and all dependencies are explicitly declared and injected at the top.Eventually we settled with AMD-style modules using require.js which offers both lazy loading and \"ahead of time\"-compilatio with r.js . 3. No jQuery-based components on page init. Nothing is more annoying than loading a page and then wait for JavaScript to modify the page before it becomes usable, forcing the user to sit and wait. Heavily optimized vanilla JavaScript components offered the speed we wanted. 4. Limited backwards-compatibility. The new API should make it easy to update existing components by providing similar interfaces, while still allowing legacy code to run side-by-side for best compatibility and to avoid rewritting everything from the start.","title":"Introduction"},{"location":"javascript/new-api_writing-a-module/#defining-a-module","text":"The default location for modules is js/ in the Core's app dir, but every app and plugin can register their own lookup path by providing the path using a template-listener on requirePaths@headIncludeJavaScript . For this example we'll assume the file is placed at js/WoltLabSuite/Core/Ui/Foo.js , the module name is therefore WoltLabSuite/Core/Ui/Foo , it is automatically derived from the file path and name. For further instructions on how to define and require modules head over to the RequireJS API . define ([ \"Ajax\" , \"WoltLabSuite/Core/Ui/Bar\" ], function ( Ajax , UiBar ) { \"use strict\" ; function Foo () { this . init (); } Foo . prototype = { init : function () { elBySel ( \".myButton\" ). addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); }, _click : function ( event ) { event . preventDefault (); if ( UiBar . isSnafucated ()) { Ajax . api ( this ); } }, _ajaxSuccess : function ( data ) { console . log ( \"Received response\" , data ); }, _ajaxSetup : function () { return { data : { actionName : \"makeSnafucated\" , className : \"wcf\\\\data\\\\foo\\\\FooAction\" } }; } } return Foo ; });","title":"Defining a Module"},{"location":"javascript/new-api_writing-a-module/#loading-a-module","text":"Modules can then be loaded through their derived name: < script data-relocate = \"true\" > require ([ \"WoltLabSuite/Core/Ui/Foo\" ], function ( UiFoo ) { new UiFoo (); }); </ script >","title":"Loading a Module"},{"location":"javascript/new-api_writing-a-module/#module-aliases","text":"Some common modules have short-hand aliases that can be used to include them without writing out their full name. You can still use their original path, but it is strongly recommended to use the aliases for consistency. Alias Full Path Ajax WoltLabSuite/Core/Ajax AjaxJsonp WoltLabSuite/Core/Ajax/Jsonp AjaxRequest WoltLabSuite/Core/Ajax/Request CallbackList WoltLabSuite/Core/CallbackList ColorUtil WoltLabSuite/Core/ColorUtil Core WoltLabSuite/Core/Core DateUtil WoltLabSuite/Core/Date/Util Devtools WoltLabSuite/Core/Devtools Dictionary WoltLabSuite/Core/Dictionary Dom/ChangeListener WoltLabSuite/Core/Dom/Change/Listener Dom/Traverse WoltLabSuite/Core/Dom/Traverse Dom/Util WoltLabSuite/Core/Dom/Util Environment WoltLabSuite/Core/Environment EventHandler WoltLabSuite/Core/Event/Handler EventKey WoltLabSuite/Core/Event/Key Language WoltLabSuite/Core/Language List WoltLabSuite/Core/List ObjectMap WoltLabSuite/Core/ObjectMap Permission WoltLabSuite/Core/Permission StringUtil WoltLabSuite/Core/StringUtil Ui/Alignment WoltLabSuite/Core/Ui/Alignment Ui/CloseOverlay WoltLabSuite/Core/Ui/CloseOverlay Ui/Confirmation WoltLabSuite/Core/Ui/Confirmation Ui/Dialog WoltLabSuite/Core/Ui/Dialog Ui/Notification WoltLabSuite/Core/Ui/Notification Ui/ReusableDropdown WoltLabSuite/Core/Ui/Dropdown/Reusable Ui/Screen WoltLabSuite/Core/Ui/Screen Ui/Scroll WoltLabSuite/Core/Ui/Scroll Ui/SimpleDropdown WoltLabSuite/Core/Ui/Dropdown/Simple Ui/TabMenu WoltLabSuite/Core/Ui/TabMenu Upload WoltLabSuite/Core/Upload User WoltLabSuite/Core/User","title":"Module Aliases"},{"location":"migration/wcf21/css/","text":"WCF 2.1.x - CSS # The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"CSS"},{"location":"migration/wcf21/css/#wcf-21x-css","text":"The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. The entire CSS has been rewritten from scratch, please read the docs on CSS to learn what has changed.","title":"WCF 2.1.x - CSS"},{"location":"migration/wcf21/package/","text":"WCF 2.1.x - Package Components # package.xml # Short Instructions # Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability. Example # <instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions> Exceptions # These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar acpMenu.xml # Renamed Categories # The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application Submenu Items # Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item. Example # <!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem> Common Icon Names # You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload box.xml # The box PIP has been added. cronjob.xml # Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete. eventListener.xml # Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete. menu.xml # The menu PIP has been added. menuItem.xml # The menuItem PIP has been added. objectType.xml # The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported. option.xml # The module.display category has been renamed into module.customization . page.xml # The page PIP has been added. pageMenu.xml # The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"Package Components"},{"location":"migration/wcf21/package/#wcf-21x-package-components","text":"","title":"WCF 2.1.x - Package Components"},{"location":"migration/wcf21/package/#packagexml","text":"","title":"package.xml"},{"location":"migration/wcf21/package/#short-instructions","text":"Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: PIP ). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. EventListenerPackageInstallationPlugin implies the filename eventListener.xml . The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. Every PIP can define a custom filename if the default value cannot be properly derived. For example the ACPMenu -pip would default to aCPMenu.xml , requiring the class to explicitly override the default filename with acpMenu.xml for readability.","title":"Short Instructions"},{"location":"migration/wcf21/package/#example","text":"<instructions type= \"install\" > <!-- assumes `eventListener.xml` --> <instruction type= \"eventListener\" /> <!-- assumes `install.sql` --> <instruction type= \"sql\" /> <!-- assumes `language/*.xml` --> <instruction type= \"language\" /> <!-- exceptions --> <!-- assumes `files.tar` --> <instruction type= \"file\" /> <!-- no default value, requires relative path --> <instruction type= \"script\" > acp/install_com.woltlab.wcf_3.0.php </instruction> </instructions>","title":"Example"},{"location":"migration/wcf21/package/#exceptions","text":"These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions. PIP Default Value acpTemplate acptemplates.tar file files.tar language language/*.xml script (No default value) sql install.sql template templates.tar","title":"Exceptions"},{"location":"migration/wcf21/package/#acpmenuxml","text":"","title":"acpMenu.xml"},{"location":"migration/wcf21/package/#renamed-categories","text":"The following categories have been renamed, menu items need to be adjusted to reflect the new names: Old Value New Value wcf.acp.menu.link.system wcf.acp.menu.link.configuration wcf.acp.menu.link.display wcf.acp.menu.link.customization wcf.acp.menu.link.community wcf.acp.menu.link.application","title":"Renamed Categories"},{"location":"migration/wcf21/package/#submenu-items","text":"Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of Add \u2026 links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item.","title":"Submenu Items"},{"location":"migration/wcf21/package/#example_1","text":"<!-- category --> <acpmenuitem name= \"wcf.acp.menu.link.group\" > <parent> wcf.acp.menu.link.user </parent> <showorder> 2 </showorder> </acpmenuitem> <!-- menu item --> <acpmenuitem name= \"wcf.acp.menu.link.group.list\" > <controller> wcf\\acp\\page\\UserGroupListPage </controller> <parent> wcf.acp.menu.link.group </parent> <permissions> admin.user.canEditGroup,admin.user.canDeleteGroup </permissions> </acpmenuitem> <!-- menu item action --> <acpmenuitem name= \"wcf.acp.menu.link.group.add\" > <controller> wcf\\acp\\form\\UserGroupAddForm </controller> <!-- actions are defined by menu items of menu items --> <parent> wcf.acp.menu.link.group.list </parent> <permissions> admin.user.canAddGroup </permissions> <!-- required FontAwesome icon name used for display --> <icon> fa-plus </icon> </acpmenuitem>","title":"Example"},{"location":"migration/wcf21/package/#common-icon-names","text":"You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. Meaning Icon Name Result Add or create fa-plus Search fa-search Upload fa-upload","title":"Common Icon Names"},{"location":"migration/wcf21/package/#boxxml","text":"The box PIP has been added.","title":"box.xml"},{"location":"migration/wcf21/package/#cronjobxml","text":"Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Cronjobs can now be assigned a name using the name attribute as in <cronjob name=\"com.woltlab.wcf.refreshPackageUpdates\"> , it will be used to identify cronjobs during an update or delete.","title":"cronjob.xml"},{"location":"migration/wcf21/package/#eventlistenerxml","text":"Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again. Event listeners can now be assigned a name using the name attribute as in <eventlistener name=\"sessionPageAccessLog\"> , it will be used to identify event listeners during an update or delete.","title":"eventListener.xml"},{"location":"migration/wcf21/package/#menuxml","text":"The menu PIP has been added.","title":"menu.xml"},{"location":"migration/wcf21/package/#menuitemxml","text":"The menuItem PIP has been added.","title":"menuItem.xml"},{"location":"migration/wcf21/package/#objecttypexml","text":"The definition com.woltlab.wcf.user.dashboardContainer has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the page.xml are valid containers and therefore there is no need for this definition anymore. The definitions com.woltlab.wcf.page and com.woltlab.wcf.user.online.location have been superseded by the page.xml , they're no longer supported.","title":"objectType.xml"},{"location":"migration/wcf21/package/#optionxml","text":"The module.display category has been renamed into module.customization .","title":"option.xml"},{"location":"migration/wcf21/package/#pagexml","text":"The page PIP has been added.","title":"page.xml"},{"location":"migration/wcf21/package/#pagemenuxml","text":"The pageMenu.xml has been superseded by the page.xml and is no longer available.","title":"pageMenu.xml"},{"location":"migration/wcf21/php/","text":"WCF 2.1.x - PHP # Message Processing # WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done. Input Processing for Storage # The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message. Embedded Objects # Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); } Rendering the Message # The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml (); Simplified Output # At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 ); Plaintext Output # The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 ); Rebuilding Data # Converting from BBCode # Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml (); Extracting Embedded Objects # The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here Breadcrumbs / Page Location # Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object ); Pages and Forms # The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work. Search # ISearchableObjectType # Added the setLocation() method that is used to set the current page location based on the search result. SearchIndexManager # The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"PHP API"},{"location":"migration/wcf21/php/#wcf-21x-php","text":"","title":"WCF 2.1.x - PHP"},{"location":"migration/wcf21/php/#message-processing","text":"WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done.","title":"Message Processing"},{"location":"migration/wcf21/php/#input-processing-for-storage","text":"The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example [b]\u2026[/b] becomes <strong>\u2026</strong> , while others are converted into a metacode tag for later processing. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); The $messageObjectID can be zero if the element did not exist before, but it should be non-zero when saving an edited message.","title":"Input Processing for Storage"},{"location":"migration/wcf21/php/#embedded-objects","text":"Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $message , $messageObjectType , $messageObjectID ); $html = $processor -> getHtml (); // at this point the message is saved to database and the created object // `$example` is a `DatabaseObject` with the id column `$exampleID` $processor -> setObjectID ( $example -> exampleID ); if ( \\wcf\\system\\message\\embedded\\object\\MessageEmbeddedObjectManager :: getInstance () -> registerObjects ( $processor )) { // there is at least one embedded object, this is also the point at which you // would set `hasEmbeddedObjects` to true (if implemented by your type) ( new \\wcf\\data\\example\\ExampleEditor ( $example )) -> update ([ 'hasEmbeddedObjects' => 1 ]); }","title":"Embedded Objects"},{"location":"migration/wcf21/php/#rendering-the-message","text":"The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID ); $renderedHtml = $processor -> getHtml ();","title":"Rendering the Message"},{"location":"migration/wcf21/php/#simplified-output","text":"At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/simplified-html' ); $processor -> process ( \u2026 );","title":"Simplified Output"},{"location":"migration/wcf21/php/#plaintext-output","text":"The text/plain output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. <? php $processor = new \\wcf\\system\\html\\output\\HtmlOutputProcessor (); $processor -> setOutputType ( 'text/plain' ); $processor -> process ( \u2026 );","title":"Plaintext Output"},{"location":"migration/wcf21/php/#rebuilding-data","text":"","title":"Rebuilding Data"},{"location":"migration/wcf21/php/#converting-from-bbcode","text":"Enabling message conversion for HTML messages is undefined and yields unexpected results. Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of process() to true . <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> process ( $html , $messageObjectType , $messageObjectID , true ); $renderedHtml = $processor -> getHtml ();","title":"Converting from BBCode"},{"location":"migration/wcf21/php/#extracting-embedded-objects","text":"The process() method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with processEmbeddedContent() . This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of process() saved to storage. <? php $processor = new \\wcf\\system\\html\\input\\HtmlInputProcessor (); $processor -> processEmbeddedContent ( $html , $messageObjectType , $messageObjectID ); // invoke `MessageEmbeddedObjectManager::registerObjects` here","title":"Extracting Embedded Objects"},{"location":"migration/wcf21/php/#breadcrumbs-page-location","text":"Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order. Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the PageLocationManager . <? php // before \\wcf\\system\\WCF :: getBreadcrumbs () -> add ( new \\wcf\\system\\breadcrumb\\Breadcrumb ( 'title' , 'link' )); // after \\wcf\\system\\page\\PageLocationManager :: getInstance () -> addParentLocation ( $pageIdentifier , $pageObjectID , $object );","title":"Breadcrumbs / Page Location"},{"location":"migration/wcf21/php/#pages-and-forms","text":"The property $activeMenuItem has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly register your pages for this feature to work.","title":"Pages and Forms"},{"location":"migration/wcf21/php/#search","text":"","title":"Search"},{"location":"migration/wcf21/php/#isearchableobjecttype","text":"Added the setLocation() method that is used to set the current page location based on the search result.","title":"ISearchableObjectType"},{"location":"migration/wcf21/php/#searchindexmanager","text":"The methods SearchIndexManager::add() and SearchIndexManager::update() have been deprecated and forward their call to the new method SearchIndexManager::set() .","title":"SearchIndexManager"},{"location":"migration/wcf21/templates/","text":"WCF 2.1.x - Templates # Page Layout # The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information. Sidebars # Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section > Forms # The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing. Icons # The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" > Changed Icon Names # Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4. Changed Classes # .dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList Removed Elements and Classes # <nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible. Simple Example # The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' } Full Example # { * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' } Content Header # There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible. Recommended Approach # { * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture } Alternative # { capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Templates"},{"location":"migration/wcf21/templates/#wcf-21x-templates","text":"","title":"WCF 2.1.x - Templates"},{"location":"migration/wcf21/templates/#page-layout","text":"The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as documentHeader , headInclude or userNotice . Instead use a simple {include file='header'} that now takes care of of the entire application frame. Templates must not include a trailing </body></html> after including the footer template. The documentHeader , headInclude and userNotice template should no longer be included manually, the same goes with the <body> element, please use {include file='header'} instead. The sidebarOrientation variable for the header template has been removed and no longer works. header.boxHeadline has been unified and now reads header.contentHeader Please see the full example at the end of this page for more information.","title":"Page Layout"},{"location":"migration/wcf21/templates/#sidebars","text":"Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of <fieldset> has been deprecated due to browser inconsistencies and bugs and should be replaced with section.box . Previous markup used in WoltLab Community Framework 2.1 and earlier: < fieldset > < legend > <!-- Title --> </ legend > < div > <!-- Content --> </ div > </ fieldset > The new markup since WoltLab Suite 3.0: < section class = \"box\" > < h2 class = \"boxTitle\" > <!-- Title --> </ h2 > < div class = \"boxContent\" > <!-- Content --> </ div > </ section >","title":"Sidebars"},{"location":"migration/wcf21/templates/#forms","text":"The input tag for session ids SID_INPUT_TAG has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in <div class=\"container containerPadding marginTop\">\u2026</div> which no longer has any effect and should be removed. If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: { include file = 'messageFormPreviewButton' previewMessageObjectType = 'com.example.foo.bar' previewMessageObjectID = 0 } The message object id should be non-zero when editing.","title":"Forms"},{"location":"migration/wcf21/templates/#icons","text":"The old .icon-<iconName> classes have been removed, you are required to use the official .fa-<iconName> class names from FontAwesome. This does not affect the generic classes .icon (indicates an icon) and .icon<size> (e.g. .icon16 that sets the dimensions), these are still required and have not been deprecated. Before: < span class = \"icon icon16 icon-list\" > Now: < span class = \"icon icon16 fa-list\" >","title":"Icons"},{"location":"migration/wcf21/templates/#changed-icon-names","text":"Quite a few icon names have been renamed, the official wiki lists the new icon names in FontAwesome 4.","title":"Changed Icon Names"},{"location":"migration/wcf21/templates/#changed-classes","text":".dataList has been replaced and should now read <ol class=\"inlineList commaSeparated\"> (same applies to <ul> ) .framedIconList has been changed into .userAvatarList","title":"Changed Classes"},{"location":"migration/wcf21/templates/#removed-elements-and-classes","text":"<nav class=\"jsClipboardEditor\"> and <div class=\"jsClipboardContainer\"> have been replaced with a floating button. The anchors a.toTopLink have been replaced with a floating button. Avatars should no longer receive the class framed The dl.condensed class, as seen in the editor tab menu, is no longer required. Anything related to sidebarCollapsed has been removed as sidebars are no longer collapsible.","title":"Removed Elements and Classes"},{"location":"migration/wcf21/templates/#simple-example","text":"The code below includes only the absolute minimum required to display a page, the content title is already included in the output. { include file = 'header' } <div class=\"section\"> Hello World! </div> { include file = 'footer' }","title":"Simple Example"},{"location":"migration/wcf21/templates/#full-example","text":"{ * The page title is automatically set using the page definition, avoid setting it if you can! If you really need to modify the title, you can still reference the original title with: {$__wcf->getActivePage()->getTitle()} * } { capture assign = 'pageTitle' } Custom Page Title { /capture } { * NOTICE: The content header goes here, see the section after this to learn more. * } { * you must not use `headContent` for JavaScript * } { capture assign = 'headContent' } <link rel=\"alternate\" type=\"application/rss+xml\" title=\" { lang } wcf.global.button.rss { /lang } \" href=\"\u2026\"> { /capture } { * optional, content will be added to the top of the left sidebar * } { capture assign = 'sidebarLeft' } \u2026 { event name = 'boxes' } { /capture } { * optional, content will be added to the top of the right sidebar * } { capture assign = 'sidebarRight' } \u2026 { event name = 'boxes' } { /capture } { capture assign = 'headerNavigation' } <li><a href=\"#\" title=\"Custom Button\" class=\"jsTooltip\"><span class=\"icon icon16 fa-check\"></span> <span class=\"invisible\">Custom Button</span></a></li> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages \u2026 } { /content } </div> { /hascontent } { * the actual content * } <div class=\"section\"> \u2026 </div> <footer class=\"contentFooter\"> { * skip this if you're not using any pagination * } { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\"\u2026\" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> <script data-relocate=\"true\"> /* any JavaScript code you need */ </script> { * do not include `</body></html>` here, the footer template is the last bit of code! * } { include file = 'footer' }","title":"Full Example"},{"location":"migration/wcf21/templates/#content-header","text":"There are two different methods to set the content header, one sets only the actual values, but leaves the outer HTML untouched, that is generated by the header template. This is the recommended approach and you should avoid using the alternative method whenever possible.","title":"Content Header"},{"location":"migration/wcf21/templates/#recommended-approach","text":"{ * This is automatically set using the page data and should not be set manually! * } { capture assign = 'contentTitle' } Custom Content Title { /capture } { capture assign = 'contentDescription' } Optional description that is displayed right after the title. { /capture } { capture assign = 'contentHeaderNavigation' } List of navigation buttons displayed right next to the title. { /capture }","title":"Recommended Approach"},{"location":"migration/wcf21/templates/#alternative","text":"{ capture assign = 'contentHeader' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Custom Content Title</h1> <p class=\"contentHeaderDescription\">Custom Content Description</p> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'CustomController' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span>Custom Button</span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { /capture }","title":"Alternative"},{"location":"migration/wsc30/css/","text":"Migrating from WSC 3.0 - CSS # New Style Variables # The new style variables are only applied to styles that have the compatibility set to WSC 3.1 wcfContentContainer # The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color wcfEditorButton # These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons Color Variables in alert.scss # The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"CSS"},{"location":"migration/wsc30/css/#migrating-from-wsc-30-css","text":"","title":"Migrating from WSC 3.0 - CSS"},{"location":"migration/wsc30/css/#new-style-variables","text":"The new style variables are only applied to styles that have the compatibility set to WSC 3.1","title":"New Style Variables"},{"location":"migration/wsc30/css/#wcfcontentcontainer","text":"The page content is encapsulated in a new container that wraps around the inner content, but excludes the sidebars, header and page navigation elements. $wcfContentContainerBackground - background color $wcfContentContainerBorder - border color","title":"wcfContentContainer"},{"location":"migration/wsc30/css/#wcfeditorbutton","text":"These variables control the appearance of the editor toolbar and its buttons. $wcfEditorButtonBackground - button and toolbar background color $wcfEditorButtonBackgroundActive - active button background color $wcfEditorButtonText - text color for available buttons $wcfEditorButtonTextActive - text color for active buttons $wcfEditorButtonTextDisabled - text color for disabled buttons","title":"wcfEditorButton"},{"location":"migration/wsc30/css/#color-variables-in-alertscss","text":"The color values for <small class=\"innerError\"> used to be hardcoded values, but have now been changed to use the values for error messages ( wcfStatusError* ) instead.","title":"Color Variables in alert.scss"},{"location":"migration/wsc30/javascript/","text":"Migrating from WSC 3.0 - JavaScript # Accelerated Guest View / Tiny Builds # The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever. Code Templates for Tiny Builds # The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now. Legacy JavaScript # if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); } require.js Modules # define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; }); Including tinified builds through {js} # The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state. Real Error Messages for AJAX Responses # The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms. Example Code # define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; }); Simplified Form Submit in Dialogs # Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field. Helper Function for Inline Error Messages # Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef) Example Code # require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"JavaScript API"},{"location":"migration/wsc30/javascript/#migrating-from-wsc-30-javascript","text":"","title":"Migrating from WSC 3.0 - JavaScript"},{"location":"migration/wsc30/javascript/#accelerated-guest-view-tiny-builds","text":"The new tiny builds are highly optimized variants of existing JavaScript files and modules, aiming for significant performance improvements for guests and search engines alike. This is accomplished by heavily restricting page interaction to read-only actions whenever possible, which in return removes the need to provide certain JavaScript modules in general. For example, disallowing guests to write any formatted messages will in return remove the need to provide the WYSIWYG editor at all. But it doesn't stop there, there are a lot of other modules that provide additional features for the editor, and by excluding the editor, we can also exclude these modules too. Long story short, the tiny mode guarantees that certain actions will never be carried out by guests or search engines, therefore some modules are not going to be needed by them ever.","title":"Accelerated Guest View / Tiny Builds"},{"location":"migration/wsc30/javascript/#code-templates-for-tiny-builds","text":"The following examples assume that you use the virtual constant COMPILER_TARGET_DEFAULT as a switch for the optimized code path. This is also the constant used by the official build scripts for JavaScript files . We recommend that you provide a mock implementation for existing code to ensure 3rd party compatibility. It is enough to provide a bare object or class that exposes the original properties using the same primitive data types. This is intended to provide a soft-fail for implementations that are not aware of the tiny mode yet, but is not required for classes that did not exist until now.","title":"Code Templates for Tiny Builds"},{"location":"migration/wsc30/javascript/#legacy-javascript","text":"if ( COMPILER_TARGET_DEFAULT ) { WCF . Example . Foo = { makeSnafucated : function () { return \"Hello World\" ; } }; WCF . Example . Bar = Class . extend ({ foobar : \"baz\" , foo : function ( $bar ) { return $bar + this . foobar ; } }); } else { WCF . Example . Foo = { makeSnafucated : function () {} }; WCF . Example . Bar = Class . extend ({ foobar : \"\" , foo : function () {} }); }","title":"Legacy JavaScript"},{"location":"migration/wsc30/javascript/#requirejs-modules","text":"define ([ \"some\" , \"fancy\" , \"dependencies\" ], function ( Some , Fancy , Dependencies ) { \"use strict\" ; if ( ! COMPILER_TARGET_DEFAULT ) { var Fake = function () {}; Fake . prototype = { init : function () {}, makeSnafucated : function () {} }; return Fake ; } function MyAwesomeClass ( niceArgument ) { this . init ( niceArgument ); } MyAwesomeClass . prototype = { init : function ( niceArgument ) { if ( niceArgument ) { this . makeSnafucated (); } }, makeSnafucated : function () { console . log ( \"Hello World\" ); } } return MyAwesomeClass ; });","title":"require.js Modules"},{"location":"migration/wsc30/javascript/#including-tinified-builds-through-js","text":"The {js} template-plugin has been updated to include support for tiny builds controlled through the optional flag hasTiny=true : {js application='wcf' file='WCF.Example' hasTiny=true} This line generates a different output depending on the debug mode and the user login-state.","title":"Including tinified builds through {js}"},{"location":"migration/wsc30/javascript/#real-error-messages-for-ajax-responses","text":"The errorMessage property in the returned response object for failed AJAX requests contained an exception-specific but still highly generic error message. This issue has been around for quite a long time and countless of implementations are relying on this false behavior, eventually forcing us to leave the value unchanged. This problem is solved by adding the new property realErrorMessage that exposes the message exactly as it was provided and now matches the value that would be displayed to users in traditional forms.","title":"Real Error Messages for AJAX Responses"},{"location":"migration/wsc30/javascript/#example-code","text":"define ([ 'Ajax' ], function ( Ajax ) { return { // ... _ajaxFailure : function ( responseData , responseText , xhr , requestData ) { console . log ( responseData . realErrorMessage ); } // ... }; });","title":"Example Code"},{"location":"migration/wsc30/javascript/#simplified-form-submit-in-dialogs","text":"Forms embedded in dialogs often do not contain the HTML <form> -element and instead rely on JavaScript click- and key-handlers to emulate a <form> -like submit behavior. This has spawned a great amount of nearly identical implementations that all aim to handle the form submit through the Enter -key, still leaving some dialogs behind. WoltLab Suite 3.1 offers automatic form submit that is enabled through a set of specific conditions and data attributes: There must be a submit button that matches the selector .formSubmit > input[type=\"submit\"], .formSubmit > button[data-type=\"submit\"] . The dialog object provided to UiDialog.open() implements the method _dialogSubmit() . Input fields require the attribute data-dialog-submit-on-enter=\"true\" to be set, the type must be one of number , password , search , tel , text or url . Clicking on the submit button or pressing the Enter -key in any watched input field will start the submit process. This is done automatically and does not require a manual interaction in your code, therefore you should not bind any click listeners on the submit button yourself. Any input field with the required attribute set will be validated to contain a non-empty string after processing the value with String.prototype.trim() . An empty field will abort the submit process and display a visible error message next to the offending field.","title":"Simplified Form Submit in Dialogs"},{"location":"migration/wsc30/javascript/#helper-function-for-inline-error-messages","text":"Displaying inline error messages on-the-fly required quite a few DOM operations that were quite simple but also super repetitive and thus error-prone when incorrectly copied over. The global helper function elInnerError() was added to provide a simple and consistent behavior of inline error messages. You can display an error message by invoking elInnerError(elementRef, \"Your Error Message\") , it will insert a new <small class=\"innerError\"> and sets the given message. If there is already an inner error present, then the message will be replaced instead. Hiding messages is done by setting the 2nd parameter to false or an empty string: elInnerError(elementRef, false) elInnerError(elementRef, '') The special values null and undefined are supported too, but their usage is discouraged, because they make it harder to understand the intention by reading the code: elInnerError(elementRef, null) elInnerError(elementRef)","title":"Helper Function for Inline Error Messages"},{"location":"migration/wsc30/javascript/#example-code_1","text":"require ([ 'Language' ], function ( Language )) { var input = elBySel ( 'input[type=\"text\"]' ); if ( input . value . trim () === '' ) { // displays a new inline error or replaces the message if there is one already elInnerError ( input , Language . get ( 'wcf.global.form.error.empty' )); } else { // removes the inline error if it exists elInnerError ( input , false ); } // the above condition is equivalent to this: elInnerError ( input , ( input . value . trim () === '' ? Language . get ( 'wcf.global.form.error.empty' ) : false )); }","title":"Example Code"},{"location":"migration/wsc30/package/","text":"Migrating from WSC 3.0 - Package Components # Cronjob Scheduler uses Server Timezone # The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel. Exclude Pages from becoming a Landing Page # Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml . Example Code # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data> New Package Installation Plugin for Media Providers # Please refer to the documentation of the mediaProvider.xml to learn more. Limited Forward-Compatibility for Plugins # Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Package Components"},{"location":"migration/wsc30/package/#migrating-from-wsc-30-package-components","text":"","title":"Migrating from WSC 3.0 - Package Components"},{"location":"migration/wsc30/package/#cronjob-scheduler-uses-server-timezone","text":"The execution time of cronjobs was previously calculated based on the coordinated universal time (UTC). This was changed in WoltLab Suite 3.1 to use the server timezone or, to be precise, the default timezone set in the administration control panel.","title":"Cronjob Scheduler uses Server Timezone"},{"location":"migration/wsc30/package/#exclude-pages-from-becoming-a-landing-page","text":"Some pages do not qualify as landing page, because they're designed around specific expectations that aren't matched in all cases. Examples include the user control panel and its sub-pages that cannot be accessed by guests and will therefore break the landing page for those. While it is somewhat to be expected from control panel pages, there are enough pages that fall under the same restrictions, but aren't easily recognized as such by an administrator. You can exclude these pages by adding <excludeFromLandingPage>1</excludeFromLandingPage> (case-sensitive) to the relevant pages in your page.xml .","title":"Exclude Pages from becoming a Landing Page"},{"location":"migration/wsc30/package/#example-code","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.example.foo.Bar\" > <!-- ... --> <excludeFromLandingPage> 1 </excludeFromLandingPage> <!-- ... --> </page> </import> </data>","title":"Example Code"},{"location":"migration/wsc30/package/#new-package-installation-plugin-for-media-providers","text":"Please refer to the documentation of the mediaProvider.xml to learn more.","title":"New Package Installation Plugin for Media Providers"},{"location":"migration/wsc30/package/#limited-forward-compatibility-for-plugins","text":"Please refer to the documentation of the <compatibility> tag in the package.xml .","title":"Limited Forward-Compatibility for Plugins"},{"location":"migration/wsc30/php/","text":"Migrating from WSC 3.0 - PHP # Approval-System for Comments # Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; } Raw HTML in User Activity Events # User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; } Permission to View Likes of an Object # Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } } Developer Tools: Sync Feature # The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } } Media Providers # Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation. Example Implementation # mediaProvider.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data> PHP Callback # The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } } Re-Evaluate HTML Messages # You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously. Example Usage # <? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"PHP API"},{"location":"migration/wsc30/php/#migrating-from-wsc-30-php","text":"","title":"Migrating from WSC 3.0 - PHP"},{"location":"migration/wsc30/php/#approval-system-for-comments","text":"Comments can now be set to require approval by a moderator before being published. This feature is disabled by default if you do not provide a permission in the manager class, enabling it requires a new permission that has to be provided in a special property of your manage implementation. <? php class ExampleCommentManager extends AbstractCommentManager { protected $permissionAddWithoutModeration = 'foo.bar.example.canAddCommentWithoutModeration' ; }","title":"Approval-System for Comments"},{"location":"migration/wsc30/php/#raw-html-in-user-activity-events","text":"User activity events were previously encapsulated inside <div class=\"htmlContent\">\u2026</div> , with impacts on native elements such as lists. You can now disable the class usage by defining your event as raw HTML: <? php class ExampleUserActivityEvent { // enables raw HTML for output, defaults to `false` protected $isRawHtml = true ; }","title":"Raw HTML in User Activity Events"},{"location":"migration/wsc30/php/#permission-to-view-likes-of-an-object","text":"Being able to view the like summary of an object was restricted to users that were able to like the object itself. This creates situations where the object type in general is likable, but the particular object cannot be liked by the current users, while also denying them to view the like summary (but it gets partly exposed through the footer note/summary!). Implement the interface \\wcf\\data\\like\\IRestrictedLikeObjectTypeProvider in your object provider to add support for this new permission check. <? php class LikeableExampleProvider extends ExampleProvider implements IRestrictedLikeObjectTypeProvider , IViewableLikeProvider { public function canViewLikes ( ILikeObject $object ) { // perform your permission checks here return true ; } }","title":"Permission to View Likes of an Object"},{"location":"migration/wsc30/php/#developer-tools-sync-feature","text":"The synchronization feature of the newly added developer tools works by invoking a package installation plugin (PIP) outside of a regular installation, while simulating the basic environment that is already exposed by the API. However, not all PIPs qualify for this kind of execution, especially because it could be invoked multiple times in a row by the user. This is solved by requiring a special marking for PIPs that have no side-effects (= idempotent) when invoked any amount of times with the same arguments. There's another feature that allows all matching PIPs to be executed in a row using a single button click. In order to solve dependencies on other PIPs, any implementing PIP must also provide the method getSyncDependencies() that returns the dependent PIPs in an arbitrary order. <? php class ExamplePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { public static function getSyncDependencies () { // provide a list of dependent PIPs in arbitrary order return []; } }","title":"Developer Tools: Sync Feature"},{"location":"migration/wsc30/php/#media-providers","text":"Media providers were added through regular SQL queries in earlier versions, but this is neither convenient, nor did it offer a reliable method to update an existing provider. WoltLab Suite 3.1 adds a new mediaProvider -PIP that also offers a className parameter to off-load the result evaluation and HTML generation.","title":"Media Providers"},{"location":"migration/wsc30/php/#example-implementation","text":"","title":"Example Implementation"},{"location":"migration/wsc30/php/#mediaproviderxml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/mediaProvider.xsd\" > <import> <provider name= \"example\" > <title> Example Provider </title> <regex> <![CDATA[https?://example.com/watch?v=(?P<ID>[a-zA-Z0-9])]]> </regex> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\ExampleBBCodeMediaProvider]]> </className> </provider> </import> </data>","title":"mediaProvider.xml"},{"location":"migration/wsc30/php/#php-callback","text":"The full match is provided for $url , while any capture groups from the regular expression are assigned to $matches . <? php class ExampleBBCodeMediaProvider implements IBBCodeMediaProvider { public function parse ( $url , array $matches = []) { return \"final HTML output\" ; } }","title":"PHP Callback"},{"location":"migration/wsc30/php/#re-evaluate-html-messages","text":"You need to manually set the disallowed bbcodes in order to avoid unintentional bbcode evaluation. Please see this commit for a reference implementation inside worker processes. The HtmlInputProcessor only supported two ways to handle an existing HTML message: Load the string through process() and run it through the validation and sanitation process, both of them are rather expensive operations and do not qualify for rebuild data workers. Detect embedded content using processEmbeddedContent() which bypasses most tasks that are carried out by process() which aren't required, but does not allow a modification of the message. The newly added method reprocess($message, $objectType, $objectID) solves this short-coming by offering a full bbcode and text re-evaluation while bypassing any input filters, assuming that the input HTML was already filtered previously.","title":"Re-Evaluate HTML Messages"},{"location":"migration/wsc30/php/#example-usage","text":"<? php // rebuild data workers tend to contain code similar to this: foreach ( $this -> objectList as $message ) { // ... if ( ! $message -> enableHtml ) { // ... } else { // OLD: $this -> getHtmlInputProcessor () -> processEmbeddedContent ( $message -> message , 'com.example.foo.message' , $message -> messageID ); // REPLACE WITH: $this -> getHtmlInputProcessor () -> reprocess ( $message -> message , 'com.example.foo.message' , $message -> messageID ); $data [ 'message' ] = $this -> getHtmlInputProcessor () -> getHtml (); } // ... }","title":"Example Usage"},{"location":"migration/wsc30/templates/","text":"Migrating from WSC 3.0 - Templates # Comment-System Overhaul # Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments. Adding Comments # Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul > Redesigned ACP User List # Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li > Sidebar Toogle-Buttons on Mobile Device # You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Templates"},{"location":"migration/wsc30/templates/#migrating-from-wsc-30-templates","text":"","title":"Migrating from WSC 3.0 - Templates"},{"location":"migration/wsc30/templates/#comment-system-overhaul","text":"Unfortunately, there has been a breaking change related to the creation of comments. You need to apply the changes below before being able to create new comments.","title":"Comment-System Overhaul"},{"location":"migration/wsc30/templates/#adding-comments","text":"Existing implementations need to include a new template right before including the generic commentList template. < ul id = \"exampleCommentList\" class = \"commentList containerList\" data- ... > {include file='commentListAddComment' wysiwygSelector='exampleCommentListAddComment'} {include file='commentList'} </ ul >","title":"Adding Comments"},{"location":"migration/wsc30/templates/#redesigned-acp-user-list","text":"Custom interaction buttons were previously added through the template event rowButtons and were merely a link-like element with an icon inside. This is still valid and supported for backwards-compatibility, but it is recommend to adapt to the new drop-down-style options using the new template event dropdownItems . <!-- button for usage with the `rowButtons` event --> < span class = \"icon icon16 fa-list jsTooltip\" title = \"Button Title\" ></ span > <!-- new drop-down item for the `dropdownItems` event --> < li >< a href = \"#\" class = \"jsMyButton\" > Button Title </ a ></ li >","title":"Redesigned ACP User List"},{"location":"migration/wsc30/templates/#sidebar-toogle-buttons-on-mobile-device","text":"You cannot override the button label for sidebars containing navigation menus. The page sidebars are automatically collapsed and presented as one or, when both sidebar are present, two condensed buttons. They use generic sidebar-related labels when open or closed, with the exception of embedded menus which will change the button label to read \"Show/Hide Navigation\". You can provide a custom label before including the sidebars by assigning the new labels to a few special variables: {assign var='__sidebarLeftShow' value='Show Left Sidebar'} {assign var='__sidebarLeftHide' value='Hide Left Sidebar'} {assign var='__sidebarRightShow' value='Show Right Sidebar'} {assign var='__sidebarRightHide' value='Hide Right Sidebar'}","title":"Sidebar Toogle-Buttons on Mobile Device"},{"location":"migration/wsc31/form-builder/","text":"Migrating from WSC 3.1 - Form Builder # Example: Two Text Form Fields # As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#migrating-from-wsc-31-form-builder","text":"","title":"Migrating from WSC 3.1 - Form Builder"},{"location":"migration/wsc31/form-builder/#example-two-text-form-fields","text":"As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each. As a reminder, here are the two relevant PHP files and the relevant template file: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ( 'success' , true ); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { include file = 'formError' } { if $success | isset } < p class = \"success\" > { lang } wcf . global . success . { $action }{ / lang } </ p > { / if } < form method = \"post\" action = \"{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object= $person }{/link}{/if}\" > < div class = \"section\" > < dl { if $errorField == 'firstName' } class = \"formError\" { / if } > < dt >< label for = \"firstName\" > { lang } wcf . person . firstName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"firstName\" name = \"firstName\" value = \" { $firstName } \" required autofocus maxlength = \"255\" class = \"long\" > { if $errorField == 'firstName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . firstName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > < dl { if $errorField == 'lastName' } class = \"formError\" { / if } > < dt >< label for = \"lastName\" > { lang } wcf . person . lastName { / lang } </ label ></ dt > < dd > < input type = \"text\" id = \"lastName\" name = \"lastName\" value = \" { $lastName } \" required maxlength = \"255\" class = \"long\" > { if $errorField == 'lastName' } < small class = \"innerError\" > { if $errorType == 'empty' } { lang } wcf . global . form . error . empty { / lang } { else } { lang } wcf . acp . person . lastName . error . { $errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > { event name = 'dataFields' } </ div > { event name = 'sections' } < div class = \"formSubmit\" > < input type = \"submit\" value = \"{lang}wcf.global.button.submit{/lang}\" accesskey = \"s\" > { @ SECURITY_TOKEN_INPUT_TAG } </ div > </ form > { include file = 'footer' } Updating the template is easy as the complete form is replace by a single line of code: { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } < header class = \"contentHeader\" > < div class = \"contentHeaderTitle\" > < h1 class = \"contentTitle\" > { lang } wcf . acp . person . { $action }{ / lang } </ h1 > </ div > < nav class = \"contentHeaderNavigation\" > < ul > < li >< a href = \"{link controller='PersonList'}{/link}\" class = \"button\" >< span class = \"icon icon16 fa-list\" ></ span > < span > { lang } wcf . acp . menu . link . person . list { / lang } </ span ></ a ></ li > { event name = 'contentHeaderNavigation' } </ ul > </ nav > </ header > { @ $form -> getHtml ()} { include file = 'footer' } PersonEditForm also becomes much simpler: only the edited Person object must be read: <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\system\\exception\\IllegalLinkException ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) { $this -> formObject = new Person ( intval ( $_REQUEST [ 'id' ])); if ( ! $this -> formObject -> personID ) { throw new IllegalLinkException (); } } } } Most of the work is done in PersonAddForm : <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractFormBuilderForm ; use wcf\\system\\form\\builder\\container\\FormContainer ; use wcf\\system\\form\\builder\\field\\TextFormField ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * @inheritDoc */ public $formAction = 'create' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectActionClass = PersonAction :: class ; /** * @inheritDoc */ protected function createForm () { parent :: createForm (); $dataContainer = FormContainer :: create ( 'data' ) -> appendChildren ([ TextFormField :: create ( 'firstName' ) -> label ( 'wcf.person.firstName' ) -> required () -> maximumLength ( 255 ), TextFormField :: create ( 'lastName' ) -> label ( 'wcf.person.lastName' ) -> required () -> maximumLength ( 255 ) ]); $this -> form -> appendChild ( $dataContainer ); } } But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm : $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set. $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object. AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.","title":"Example: Two Text Form Fields"},{"location":"migration/wsc31/like/","text":"Migrating from WSC 3.1 - Like System # Introduction # With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments. Limitations if no adjustments are made to the existing code # If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes Migration # Notifications # Mark notification as compatible # Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again. Language Variables # Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # {prefix}.like.title Reaction to a {objectName} {prefix}.like.title.stacked {#$count} users reacted to your {objectName} {prefix}.like.message {@$author->getAnchorTag()} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} others{/if} reacted to your {objectName} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{objectTypeName}.like.notification.like Notify me when someone reacted to my {objectName} German # {prefix}.like.title Reaktion auf einen {objectName} {prefix}.like.title.stacked {#$count} Benutzern haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert {prefix}.like.message {@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). {prefix}.like.message.stacked {if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}\u00d7{#$count}{/implode}). wcf.user.notification.{object_type_name}.like.notification.like Jemandem hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}dein(en){else}Ihr(en){/if} {objectName} reagiert Recent Activity # To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core. English # wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}. German # wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert. Comments # If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type> Forward Compatibility # So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#migrating-from-wsc-31-like-system","text":"","title":"Migrating from WSC 3.1 - Like System"},{"location":"migration/wsc31/like/#introduction","text":"With version 5.2 of WoltLab Suite Core the like system was completely replaced by the new reactions system. This makes it necessary to make some adjustments to existing code so that your plugin integrates completely into the new system. However, we have kept these adjustments as small as possible so that it is possible to use the reaction system with slight restrictions even without adjustments.","title":"Introduction"},{"location":"migration/wsc31/like/#limitations-if-no-adjustments-are-made-to-the-existing-code","text":"If no adjustments are made to the existing code, the following functions are not available: * Notifications about reactions/likes * Recent Activity Events for reactions/likes","title":"Limitations if no adjustments are made to the existing code"},{"location":"migration/wsc31/like/#migration","text":"","title":"Migration"},{"location":"migration/wsc31/like/#notifications","text":"","title":"Notifications"},{"location":"migration/wsc31/like/#mark-notification-as-compatible","text":"Since there are no more likes with the new version, it makes no sense to send notifications about it. Instead of notifications about likes, notifications about reactions are now sent. However, this only changes the notification text and not the notification itself. To update the notification, we first add the interface \\wcf\\data\\reaction\\object\\IReactionObject to the \\wcf\\data\\like\\object\\ILikeObject object (e.g. in WoltLab Suite Forum we added the interface to the class \\wbb\\data\\post\\LikeablePost ). After that the object is marked as \"compatible with WoltLab Suite Core 5.2\" and notifications about reactions are sent again.","title":"Mark notification as compatible"},{"location":"migration/wsc31/like/#language-variables","text":"Next, to display all reactions for the current notification in the notification text, we include the trait \\wcf\\system\\user\\notification\\event\\TReactionUserNotificationEvent in the user notification event class (typically named like *LikeUserNotificationEvent ). These trait provides a new function that reads out and groups the reactions. The result of this function must now only be passed to the language variable. The name \"reactions\" is typically used as the variable name for the language variable. As a final step, we only need to change the language variables themselves. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Language Variables"},{"location":"migration/wsc31/like/#recent-activity","text":"To adjust entries in the Recent Activity, only three small steps are necessary. First we pass the concrete reaction to the language variable, so that we can use the reaction object there. To do this, we add the following variable to the text of the \\wcf\\system\\user\\activity\\event\\IUserActivityEvent object: $event->reactionType . Typically we name the variable reactionType . In the second step, we mark the event as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.example.likeableObject.recentActivityEvent </name> <definitionname> com.woltlab.wcf.user.recentActivityEvent </definitionname> <classname> wcf\\system\\user\\activity\\event\\LikeableObjectUserActivityEvent </classname> <supportsReactions> 1 </supportsReactions> </type> Finally we modify our language variable. To ensure a consistent usability, the same formulations should be used as in the WoltLab Suite Core.","title":"Recent Activity"},{"location":"migration/wsc31/like/#english_1","text":"wcf.user.recentActivity.{object_type_name}.recentActivityEvent Reaction ({objectName}) Your language variable for the recent activity text Reacted with <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> to the {objectName}.","title":"English"},{"location":"migration/wsc31/like/#german_1","text":"wcf.user.recentActivity.{objectTypeName}.recentActivityEvent Reaktion ({objectName}) Your language variable for the recent activity text Hat mit <span title=\"{$reactionType->getTitle()}\" class=\"jsTooltip\">{@$reactionType->renderIcon()}</span> auf {objectName} reagiert.","title":"German"},{"location":"migration/wsc31/like/#comments","text":"If comments send notifications, they must also be updated. The language variables are changed in the same way as described in the section Notifications / Language . After that comment must be marked as compatible. Therefore we set the parameter supportsReactions in the objectType.xml to 1 . So for example the entry looks like this: <type> <name> com.woltlab.wcf.objectComment.response.like.notification </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> wcf\\system\\user\\notification\\object\\type\\LikeUserNotificationObjectType </classname> <category> com.woltlab.example </category> <supportsReactions> 1 </supportsReactions> </type>","title":"Comments"},{"location":"migration/wsc31/like/#forward-compatibility","text":"So that these changes also work in older versions of WoltLab Suite Core, the used classes and traits were backported with WoltLab Suite Core 3.0.22 and WoltLab Suite Core 3.1.10.","title":"Forward Compatibility"},{"location":"migration/wsc31/php/","text":"Migrating from WSC 3.1 - PHP # Form Builder # WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway. Like System # WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here . User Content Providers # User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects. PHP Class # First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } } Object Type # Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type: nicevalue # Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first. hidden # Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically! requiredobjecttype # Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important. PHP Database API # WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP API"},{"location":"migration/wsc31/php/#migrating-from-wsc-31-php","text":"","title":"Migrating from WSC 3.1 - PHP"},{"location":"migration/wsc31/php/#form-builder","text":"WoltLab Suite Core 5.2 introduces a new, simpler and quicker way of creating forms: form builder . You can find examples of how to migrate existing forms to form builder here . In the near future, to ensure backwards compatibility within WoltLab packages, we will only use form builder for new forms or for major rewrites of existing forms that would break backwards compatibility anyway.","title":"Form Builder"},{"location":"migration/wsc31/php/#like-system","text":"WoltLab Suite Core 5.2 replaced the like system with the reaction system. You can find the migration guide here .","title":"Like System"},{"location":"migration/wsc31/php/#user-content-providers","text":"User content providers help the WoltLab Suite to find user generated content. They provide a class with which you can find content from a particular user and delete objects.","title":"User Content Providers"},{"location":"migration/wsc31/php/#php-class","text":"First, we create the PHP class that provides our interface to provide the data. The class must implement interface wcf\\system\\user\\content\\provider\\IUserContentProvider in any case. Mostly we process data which is based on wcf\\data\\DatabaseObject . In this case, the WoltLab Suite provides an abstract class wcf\\system\\user\\content\\provider\\AbstractDatabaseUserContentProvider that can be used to automatically generates the standardized classes to generate the list and deletes objects via the DatabaseObjectAction. For example, if we would create a content provider for comments, the class would look like this: <? php namespace wcf\\system\\user\\content\\provider ; use wcf\\data\\comment\\Comment ; /** * User content provider for comments. * * @author Joshua Ruesweg * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\User\\Content\\Provider * @since 5.2 */ class CommentUserContentProvider extends AbstractDatabaseUserContentProvider { /** * @inheritdoc */ public static function getDatabaseObjectClass () { return Comment :: class ; } }","title":"PHP Class"},{"location":"migration/wsc31/php/#object-type","text":"Now the appropriate object type must be created for the class. This object type must be from the definition com.woltlab.wcf.content.userContentProvider and include the previous created class as FQN in the parameter classname . Also the following parameters can be used in the object type:","title":"Object Type"},{"location":"migration/wsc31/php/#nicevalue","text":"Optional The nice value is used to determine the order in which the remove content worker are execute the provider. Content provider with lower nice values are executed first.","title":"nicevalue"},{"location":"migration/wsc31/php/#hidden","text":"Optional Specifies whether or not this content provider can be actively selected in the Content Remove Worker. If it cannot be selected, it will not be executed automatically!","title":"hidden"},{"location":"migration/wsc31/php/#requiredobjecttype","text":"Optional The specified list of comma-separated object types are automatically removed during content removal when this object type is being removed. Attention : The order of removal is undefined by default, specify a nicevalue if the order is important.","title":"requiredobjecttype"},{"location":"migration/wsc31/php/#php-database-api","text":"WoltLab Suite 5.2 introduces a new way to update the database scheme: database PHP API .","title":"PHP Database API"},{"location":"migration/wsc52/libraries/","text":"Migrating from WSC 5.2 - Third Party Libraries # SCSS Compiler # WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 . Guzzle # WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Third Party Libraries"},{"location":"migration/wsc52/libraries/#migrating-from-wsc-52-third-party-libraries","text":"","title":"Migrating from WSC 5.2 - Third Party Libraries"},{"location":"migration/wsc52/libraries/#scss-compiler","text":"WoltLab Suite Core 5.3 upgrades the bundled SCSS compiler from leafo/scssphp 0.7.x to scssphp/scssphp 1.1.x. With the updated composer package name the SCSS compiler also received updated namespaces. WoltLab Suite Core adds a compatibility layer that maps the old namespace to the new namespace. The classes themselves appear to be drop-in compatible. Exceptions cannot be mapped using this compatibility layer, any catch blocks catching a specific Exception within the Leafo namespace will need to be adjusted. More details can be found in the Pull Request WoltLab/WCF#3415 .","title":"SCSS Compiler"},{"location":"migration/wsc52/libraries/#guzzle","text":"WoltLab Suite Core 5.3 ships with a bundled version of Guzzle 6 . Going forward using Guzzle is the recommended way to perform HTTP requests. The \\wcf\\util\\HTTPRequest class should no longer be used and transparently uses Guzzle under the hood. Use \\wcf\\system\\io\\HttpFactory to retrieve a correctly configured GuzzleHttp\\ClientInterface . Please note that it is recommended to explicitely specify a sink when making requests, due to a PHP / Guzzle bug. Have a look at the implementation in WoltLab/WCF for an example.","title":"Guzzle"},{"location":"migration/wsc52/php/","text":"Migrating from WSC 5.2 - PHP # Comments # The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager . Event Listeners # The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener . Email Activation # Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() . *AddForm # WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]); User Generated Links # It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available. Resource Management When Scaling Images # It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details. Toggle for Accelerated Mobile Pages (AMP) # Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"PHP API"},{"location":"migration/wsc52/php/#migrating-from-wsc-52-php","text":"","title":"Migrating from WSC 5.2 - PHP"},{"location":"migration/wsc52/php/#comments","text":"The ICommentManager::isContentAuthor(Comment|CommentResponse): bool method was added. A default implementation that always returns false is available when inheriting from AbstractCommentManager . It is strongly recommended to implement isContentAuthor within your custom comment manager. An example implementation can be found in ArticleCommentManager .","title":"Comments"},{"location":"migration/wsc52/php/#event-listeners","text":"The AbstractEventListener class was added. AbstractEventListener contains an implementation of execute() that will dispatch the event handling to dedicated methods based on the $eventName and, in case of the event object being an AbstractDatabaseObjectAction , the action name. Find the details of the dispatch behavior within the class comment of AbstractEventListener .","title":"Event Listeners"},{"location":"migration/wsc52/php/#email-activation","text":"Starting with WoltLab Suite 5.3 the user activation status is independent of the email activation status. A user can be activated even though their email address has not been confirmed, preventing emails being sent to these users. Going forward the new User::isEmailConfirmed() method should be used to check whether sending automated emails to this user is acceptable. If you need to check the user's activation status you should use the new method User::pendingActivation() instead of relying on activationCode . To check, which type of activation is missing, you can use the new methods User::requiresEmailActivation() and User::requiresAdminActivation() .","title":"Email Activation"},{"location":"migration/wsc52/php/#addform","text":"WoltLab Suite 5.3 provides a new framework to allow the administrator to easily edit newly created objects by adding an edit link to the success message. To support this edit link two small changes are required within your *AddForm . Update the template. Replace: { include file = 'formError' } { if $success | isset } <p class=\"success\"> { lang } wcf.global.success. { $action }{ /lang } </p> { /if } With: { include file = 'formNotice' } Expose objectEditLink to the template. Example ( $object being the newly created object): WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( ObjectEditForm :: class , [ 'id' => $object -> objectID ]), ]);","title":"*AddForm"},{"location":"migration/wsc52/php/#user-generated-links","text":"It is recommended by search engines to mark up links within user generated content using the rel=\"ugc\" attribute to indicate that they might be less trustworthy or spammy. WoltLab Suite 5.3 will automatically sets that attribute on external links during message output processing. Set the new HtmlOutputProcessor::$enableUgc property to false if the type of message is not user-generated content, but restricted to a set of trustworthy users. An example of such a type of message would be official news articles. If you manually generate links based off user input you need to specify the attribute yourself. The $isUgc attribute was added to StringUtil::getAnchorTag(string, string, bool, bool): string , allowing you to easily generate a correct anchor tag. If you need to specify additional HTML attributes for the anchor tag you can use the new StringUtil::getAnchorTagAttributes(string, bool): string method to generate the anchor attributes that are dependent on the target URL. Specifically the attributes returned are the class=\"externalURL\" attribute, the rel=\"\u2026\" attribute and the target=\"\u2026\" attribute. Within the template the {anchorAttributes} template plugin is newly available.","title":"User Generated Links"},{"location":"migration/wsc52/php/#resource-management-when-scaling-images","text":"It was discovered that the code holds references to scaled image resources for an unnecessarily long time, taking up memory. This becomes especially apparent when multiple images are scaled within a loop, reusing the same variable name for consecutive images. Unless the destination variable is explicitely cleared before processing the next image up to two images will be stored in memory concurrently. This possibly causes the request to exceed the memory limit or ImageMagick's internal resource limits, even if sufficient resources would have been available to scale the current image. Starting with WoltLab Suite 5.3 it is recommended to clear image handles as early as possible. The usual pattern of creating a thumbnail for an existing image would then look like this: <? php foreach ([ 200 , 500 ] as $size ) { $adapter = ImageHandler :: getInstance () -> getAdapter (); $adapter -> loadFile ( $src ); $thumbnail = $adapter -> createThumbnail ( $size , $size , true ); $adapter -> writeImage ( $thumbnail , $destination ); // New: Clear thumbnail as soon as possible to free up the memory. $thumbnail = null ; } Refer to WoltLab/WCF#3505 for additional details.","title":"Resource Management When Scaling Images"},{"location":"migration/wsc52/php/#toggle-for-accelerated-mobile-pages-amp","text":"Controllers delivering AMP versions of pages have to check for the new option MODULE_AMP and the templates of the non-AMP versions have to also check if the option is enabled before outputting the <link rel=\"amphtml\" /> element.","title":"Toggle for Accelerated Mobile Pages (AMP)"},{"location":"migration/wsc52/templates/","text":"Migrating from WSC 5.2 - Templates and Languages # {jslang} # Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script> Template Plugins # The {anchor} , {plural} , and {user} template plugins have been added. Notification Language Items # In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language } Popovers # Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Templates and Languages"},{"location":"migration/wsc52/templates/#migrating-from-wsc-52-templates-and-languages","text":"","title":"Migrating from WSC 5.2 - Templates and Languages"},{"location":"migration/wsc52/templates/#jslang","text":"Starting with WoltLab Suite 5.3 the {jslang} template plugin is available. {jslang} works like {lang} , with the difference that the result is automatically encoded for use within a single quoted JavaScript string. Before: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{lang}app.foo.bar{/lang}' , } ); // \u2026 } ); </script> After: <script> require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); </script>","title":"{jslang}"},{"location":"migration/wsc52/templates/#template-plugins","text":"The {anchor} , {plural} , and {user} template plugins have been added.","title":"Template Plugins"},{"location":"migration/wsc52/templates/#notification-language-items","text":"In addition to using the new template plugins mentioned above, language items for notifications have been further simplified. As the whole notification is clickable now, all a elements have been replaced with strong elements in notification messages. The template code to output reactions has been simplified by introducing helper methods: { * old * } { implode from = $reactions key = reactionID item = count }{ @ $__wcf -> getReactionHandler ()-> getReactionTypeByID ( $reactionID )-> renderIcon () } \u00d7 { # $count }{ /implode } { * new * } { @ $__wcf -> getReactionHandler ()-> renderInlineList ( $reactions ) } { * old * } <span title=\" { $like -> getReactionType ()-> getTitle () } \" class=\"jsTooltip\"> { @ $like -> getReactionType ()-> renderIcon () } </span> { * new * } { @ $like -> render () } Similarly, showing labels is now also easier due to the new render method: { * old * } <span class=\"label badge { if $label -> getClassNames () } { $label -> getClassNames () }{ /if } \"> { $label -> getTitle () } </span> { * new * } { @ $label -> render () } The commonly used template code { if $count < 4 }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $count != 1 }{ if $count == 2 && ! $guestTimesTriggered } and { else } , { /if }{ @ $authors [ 1 ]-> getAnchorTag () }{ if $count == 3 }{ if ! $guestTimesTriggered } and { else } , { /if } { @ $authors [ 2 ]-> getAnchorTag () }{ /if }{ /if }{ if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ else }{ @ $authors [ 0 ]-> getAnchorTag () }{ if $guestTimesTriggered } , { else } and { /if } { # $others } other users { if $guestTimesTriggered } and { if $guestTimesTriggered == 1 } a guest { else } guests { /if }{ /if }{ /if } in stacked notification messages can be replaced with a new language item: { @ 'wcf.user.notification.stacked.authorList' | language }","title":"Notification Language Items"},{"location":"migration/wsc52/templates/#popovers","text":"Popovers provide additional information of the linked object when a user hovers over a link. We unified the approach for such links: The relevant DBO class implements wcf\\data\\IPopoverObject . The relevant DBO action class implements wcf\\data\\IPopoverAction and the getPopover() method returns an array with popover content. Globally available, WoltLabSuite/Core/Controller/Popover is initialized with the relevant data. Links are created with the anchor template plugin with an additional class attribute whose value is the return value of IPopoverObject::getPopoverLinkClass() . Example: class Foo extends DatabaseObject implements IPopoverObject { public function getPopoverLinkClass () { return 'fooLink' ; } } class FooAction extends AbstractDatabaseObjectAction implements IPopoverAction { public function validateGetPopover () { // \u2026 } public function getPopover () { return [ 'template' => '\u2026' , ]; } } require ([ 'WoltLabSuite/Core/Controller/Popover' ], function ( ControllerPopover ) { ControllerPopover . init ({ className : 'fooLink' , dboAction : 'wcf\\\\data\\\\foo\\\\FooAction' , identifier : 'com.woltlab.wcf.foo' }); }); { anchor object = $foo class = 'fooLink' }","title":"Popovers"},{"location":"migration/wsc53/javascript/","text":"Migrating from WSC 5.3 - JavaScript # WCF_CLICK_EVENT # For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev )); WCF.Action.Delete and WCF.Action.Toggle # WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" { * * } data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody> { foreach from = $objects item = foo } <tr class=\"jsObjectActionObject\" data-object-id=\" { @ $foo -> getObjectID () } \"> <td class=\"columnIcon\"> { objectAction action = \"toggle\" isDisabled = $foo -> isDisabled } { objectAction action = \"delete\" objectTitle = $foo -> getTitle () } { * \u2026 * } </td> { * \u2026 * } </tr> { /foreach } </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 . WCF.Table.EmptyTableHandler # When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"JavaScript"},{"location":"migration/wsc53/javascript/#migrating-from-wsc-53-javascript","text":"","title":"Migrating from WSC 5.3 - JavaScript"},{"location":"migration/wsc53/javascript/#wcf_click_event","text":"For event listeners on click events, WCF_CLICK_EVENT is deprecated and should no longer be used. Instead, use click directly: // before element . addEventListener ( WCF_CLICK_EVENT , this . _click . bind ( this )); // after element . addEventListener ( 'click' , ( ev ) => this . _click ( ev ));","title":"WCF_CLICK_EVENT"},{"location":"migration/wsc53/javascript/#wcfactiondelete-and-wcfactiontoggle","text":"WCF.Action.Delete and WCF.Action.Toggle were used for buttons to delete or enable/disable objects via JavaScript. In each template, WCF.Action.Delete or WCF.Action.Toggle instances had to be manually created for each object listing. With version 5.4 of WoltLab Suite, we have added a CSS selector-based global TypeScript module that only requires specific CSS classes to be added to the HTML structure for these buttons to work. Additionally, we have added a new {objectAction} template plugin, which generates these buttons reducing the amount of boilerplate template code. The required base HTML structure is as follows: A .jsObjectActionContainer element with a data-object-action-class-name attribute that contains the name of PHP class that executes the actions. .jsObjectActionObject elements within .jsObjectActionContainer that represent the objects for which actions can be executed. Each .jsObjectActionObject element must have a data-object-id attribute with the id of the object. .jsObjectAction elements within .jsObjectActionObject for each action with a data-object-action attribute with the name of the action. These elements can be generated with the {objectAction} template plugin for the delete and toggle action. Example: <table class=\"table jsObjectActionContainer\" { * * } data-object-action-class-name=\"wcf\\data\\foo\\FooAction\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody> { foreach from = $objects item = foo } <tr class=\"jsObjectActionObject\" data-object-id=\" { @ $foo -> getObjectID () } \"> <td class=\"columnIcon\"> { objectAction action = \"toggle\" isDisabled = $foo -> isDisabled } { objectAction action = \"delete\" objectTitle = $foo -> getTitle () } { * \u2026 * } </td> { * \u2026 * } </tr> { /foreach } </tbody> </table> Please refer to the documentation in ObjectActionFunctionTemplatePlugin for details and examples on how to use this template plugin. The relevant TypeScript module registering the event listeners on the object action buttons is Ui/Object/Action . When an action button is clicked, an AJAX request is sent using the PHP class name and action name. After the successful execution of the action, the page is either reloaded if the action button has a data-object-action-success=\"reload\" attribute or an event using the EventHandler module is fired using WoltLabSuite/Core/Ui/Object/Action as the identifier and the object action name. Ui/Object/Action/Delete and Ui/Object/Action/Toggle listen to these events and update the user interface depending on the execute action by removing the object or updating the toggle button, respectively. Converting from WCF.Action.* to the new approach requires minimal changes per template, as shown in the relevant pull request #4080 .","title":"WCF.Action.Delete and WCF.Action.Toggle"},{"location":"migration/wsc53/javascript/#wcftableemptytablehandler","text":"When all objects in a table or list are deleted via their delete button or clipboard actions, an empty table or list can remain. Previously, WCF.Table.EmptyTableHandler had to be explicitly used in each template for these tables and lists to reload the page. As a TypeScript-based replacement for WCF.Table.EmptyTableHandler that is only initialized once globally, WoltLabSuite/Core/Ui/Empty was added. To use this new module, you only have to add the CSS class jsReloadPageWhenEmpty to the relevant HTML element. Once this HTML element no longer has child elements, the page is reloaded. To also cover scenarios in which there are fixed child elements that should not be considered when determining if there are no child elements, the data-reload-page-when-empty=\"ignore\" can be set for these elements. Examples: <table class=\"table\"> <thead> <tr> { * \u2026 * } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = object } <tr> { * \u2026 * } </tr> { /foreach } </tbody> </table> <div class=\"section tabularBox messageGroupList\"> <ol class=\"tabularList jsReloadPageWhenEmpty\"> <li class=\"tabularListRow tabularListRowHead\" data-reload-page-when-empty=\"ignore\"> { * \u2026 * } </li> { foreach from = $objects item = object } <li> { * \u2026 * } </li> { /foreach } </ol> </div>","title":"WCF.Table.EmptyTableHandler"},{"location":"migration/wsc53/libraries/","text":"Migrating from WSC 5.3 - Third Party Libraries # Guzzle # The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7. Emogrifier / CSS Inliner # The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases. scssphp # scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details. Constant Time Encoder # WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Third Party Libraries"},{"location":"migration/wsc53/libraries/#migrating-from-wsc-53-third-party-libraries","text":"","title":"Migrating from WSC 5.3 - Third Party Libraries"},{"location":"migration/wsc53/libraries/#guzzle","text":"The bundled Guzzle version was updated to Guzzle 7. No breaking changes are expected for simple uses. A detailed Guzzle migration guide can be found in the Guzzle documentation. The explicit sink that was recommended in the migration guide for WSC 5.2 can now be removed, as the Guzzle issue #2735 was fixed in Guzzle 7.","title":"Guzzle"},{"location":"migration/wsc53/libraries/#emogrifier-css-inliner","text":"The Emogrifier library was updated from version 2.2 to 5.0. This update comes with a breaking change, as the Emogrifier class was removed. With the updated Emogrifier library, the CssInliner class must be used instead. No compatibility layer was added for the Emogrifier class, as the Emogrifier library's purpose was to be used within the email subsystem of WoltLab Suite. In case you use Emogrifier directly within your own code, you will need to adjust the usage. Refer to the Emogrifier CHANGELOG and WoltLab/WCF #3738 if you need help making the necessary adjustments. If you only use Emogrifier indirectly by sending HTML mail via the email subsystem then you might notice unexpected visual changes due to the improved CSS support. Double check your CSS declarations and particularly the specificity of your selectors in these cases.","title":"Emogrifier / CSS Inliner"},{"location":"migration/wsc53/libraries/#scssphp","text":"scssphp was updated from version 1.1 to 1.4. If you interact with scssphp only by deploying .scss files, then you should not experience any breaking changes, except when the improved SCSS compatibility interprets your SCSS code differently. If you happen to directly use scssphp in your PHP code, you should be aware that scssphp deprecated the use of output formatters in favor of a simple output style enum. Refer to WoltLab/WCF #3851 and the scssphp releases for details.","title":"scssphp"},{"location":"migration/wsc53/libraries/#constant-time-encoder","text":"WoltLab Suite 5.4 ships the paragonie/constant_time_encoding library . It is recommended to use this library to perform encoding and decoding of secrets to prevent leaks via cache timing attacks. Refer to the library author\u2019s blog post for more background detail. For the common case of encoding the bytes taken from a CSPRNG in hexadecimal form, the required change would look like the following: Previously: <? php $encoded = hex2bin ( random_bytes ( 16 )); Now: <? php use ParagonIE\\ConstantTime\\Hex ; // For security reasons you should add the backslash // to ensure you refer to the `random_bytes` function // within the global namespace and not a function // defined in the current namespace. $encoded = Hex :: encode ( \\random_bytes ( 16 )); Please refer to the documentation and source code of the paragonie/constant_time_encoding library to learn how to use the library with different encodings (e.g. base64).","title":"Constant Time Encoder"},{"location":"migration/wsc53/php/","text":"Migrating from WSC 5.3 - PHP # Minimum requirements # The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details. Flood Control # To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class! DatabasePackageInstallationPlugin # DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file. PHP Database API # The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed. Captcha # The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details. Search # The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details. Styles # The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details. Tags # Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details. Avatars # The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"PHP API"},{"location":"migration/wsc53/php/#migrating-from-wsc-53-php","text":"","title":"Migrating from WSC 5.3 - PHP"},{"location":"migration/wsc53/php/#minimum-requirements","text":"The minimum requirements have been increased to the following: PHP: 7.2.24 MySQL: 5.7.31 or 8.0.19 MariaDB: 10.1.44 Most notably PHP 7.2 contains usable support for scalar types by the addition of nullable types in PHP 7.1 and parameter type widening in PHP 7.2. It is recommended to make use of scalar types and other newly introduced features whereever possible. Please refer to the PHP documentation for details.","title":"Minimum requirements"},{"location":"migration/wsc53/php/#flood-control","text":"To prevent users from creating massive amounts of contents in short periods of time, i.e., spam, existing systems already use flood control mechanisms to limit the amount of contents created within a certain period of time. With WoltLab Suite 5.4, we have added a general API that manages such rate limiting. Leveraging this API is easily done. Register an object type for the definition com.woltlab.wcf.floodControl : com.example.foo.myContent . Whenever the active user creates content of this type, call FloodControl :: getInstance () -> registerContent ( 'com.example.foo.myContent' ); You should only call this method if the user creates the content themselves. If the content is automatically created by the system, for example when copying / duplicating existing content, no activity should be registered. To check the last time when the active user created content of the relevant type, use FloodControl :: getInstance () -> getLastTime ( 'com.example.foo.myContent' ); If you want to limit the number of content items created within a certain period of time, for example within one day, use $data = FloodControl :: getInstance () -> countContent ( 'com.example.foo.myContent' , new \\DateInterval ( 'P1D' )); // number of content items created within the last day $count = $data [ 'count' ]; // timestamp when the earliest content item was created within the last day $earliestTime = $data [ 'earliestTime' ]; The method also returns earliestTime so that you can tell the user in the error message when they are able again to create new content of the relevant type. Flood control entries are only stored for 31 days and older entries are cleaned up daily. The previously mentioned methods of FloodControl use the active user and the current timestamp as reference point. FloodControl also provides methods to register content or check flood control for other registered users or for guests via their IP address. For further details on these methods, please refer to the documentation in the FloodControl class . Do not interact directly with the flood control database table but only via the FloodControl class!","title":"Flood Control"},{"location":"migration/wsc53/php/#databasepackageinstallationplugin","text":"DatabasePackageInstallationPlugin is a new idempotent package installation plugin (thus it is available in the sync function in the devtools) to update the database schema using the PHP-based database API. DatabasePackageInstallationPlugin is similar to ScriptPackageInstallationPlugin by requiring a PHP script that is included during the execution of the script. The script is expected to return an array of DatabaseTable objects representing the schema changes so that in contrast to using ScriptPackageInstallationPlugin , no DatabaseTableChangeProcessor object has to be created. The PHP file must be located in the acp/database/ directory for the devtools sync function to recognize the file.","title":"DatabasePackageInstallationPlugin"},{"location":"migration/wsc53/php/#php-database-api","text":"The PHP API to add and change database tables during package installations and updates in the wcf\\system\\database\\table namespace now also supports renaming existing table columns with the new IDatabaseTableColumn::renameTo() method: PartialDatabaseTable :: create ( 'wcf1_test' ) -> columns ([ NotNullInt10DatabaseTableColumn :: create ( 'oldName' ) -> renameTo ( 'newName' ) ]); Like with every change to existing database tables, packages can only rename columns that they installed.","title":"PHP Database API"},{"location":"migration/wsc53/php/#captcha","text":"The reCAPTCHA v1 implementation was completely removed. This includes the \\wcf\\system\\recaptcha\\RecaptchaHandler class (not to be confused with the one in the captcha namespace). The reCAPTCHA v1 endpoints have already been turned off by Google and always return a HTTP 404. Thus the implementation was completely non-functional even before this change. See WoltLab/WCF#3781 for details.","title":"Captcha"},{"location":"migration/wsc53/php/#search","text":"The generic implementation in the AbstractSearchEngine::parseSearchQuery() method was dangerous, because it did not have knowledge about the search engine\u2019s specifics. The implementation was completely removed: AbstractSearchEngine::parseSearchQuery() now always throws a \\BadMethodCallException . If you implemented a custom search engine and relied on this method, you can inline the previous implementation to preserve existing behavior. You should take the time to verify the rewritten queries against the manual of the search engine to make sure it cannot generate malformed queries or security issues. See WoltLab/WCF#3815 for details.","title":"Search"},{"location":"migration/wsc53/php/#styles","text":"The StyleCompiler class is marked final now. The internal SCSS compiler object being stored in the $compiler property was a design issue that leaked compiler state across multiple compiled styles, possibly causing misgenerated stylesheets. As the removal of the $compiler property effectively broke compatibility within the StyleCompiler and as the StyleCompiler never was meant to be extended, it was marked final. See WoltLab/WCF#3929 for details.","title":"Styles"},{"location":"migration/wsc53/php/#tags","text":"Use of the wcf1_tag_to_object.languageID column is deprecated. The languageID column is redundant, because its value can be derived from the tagID . With WoltLab Suite 5.4, it will no longer be part of any indices, allowing more efficient index usage in the general case. If you need to filter the contents of wcf1_tag_to_object by language, you should perform an INNER JOIN wcf1_tag tag ON tag.tagID = tag_to_object.tagID and filter on wcf1_tag.languageID . See WoltLab/WCF#3904 for details.","title":"Tags"},{"location":"migration/wsc53/php/#avatars","text":"The ISafeFormatAvatar interface was added to properly support fallback image types for use in emails. If your custom IUserAvatar implementation supports image types without broad support (i.e. anything other than PNG, JPEG, and GIF), then you should implement the ISafeFormatAvatar interface to return a fallback PNG, JPEG, or GIF image. See WoltLab/WCF#4001 for details.","title":"Avatars"},{"location":"migration/wsc53/session/","text":"Migrating from WSC 5.3 - Session Handling and Authentication # WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data. Summary and Concepts # Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other. Legacy Persistent Login # The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight. Multiple Sessions # To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler . Merged ACP and Frontend Sessions # WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa. Improved Authentication and Reauthentication # WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence. Additions and Changes # Password Hashing # WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems. Session Storage # The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class! Reauthentication # For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 . Multi-factor Authentication # To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in. Adding Multi-factor Methods # Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions. Deprecations and Removals # SessionHandler # Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details. ACP Sessions # The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions. Cookies # The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed. Virtual Sessions # The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables. Security Token Constants # The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions. PasswordUtil and Double BCrypt Hashes # Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"Session Handling and Authentication"},{"location":"migration/wsc53/session/#migrating-from-wsc-53-session-handling-and-authentication","text":"WoltLab Suite 5.4 includes a completely refactored session handling. As long as you only interact with sessions via WCF::getSession() , especially when you perform read-only accesses, you should not notice any breaking changes. You might appreciate some of the new session methods if you process security sensitive data.","title":"Migrating from WSC 5.3 - Session Handling and Authentication"},{"location":"migration/wsc53/session/#summary-and-concepts","text":"Most of the changes revolve around the removal of the legacy persistent login functionality and the assumption that every user has a single session only. Both aspects are related to each other.","title":"Summary and Concepts"},{"location":"migration/wsc53/session/#legacy-persistent-login","text":"The legacy persistent login was rather an automated login. Upon bootstrapping a session, it was checked whether the user had a cookie pair storing the user\u2019s userID and (a single BCrypt hash of) the user\u2019s password. If such a cookie pair exists and the BCrypt hash within the cookie matches the user\u2019s password hash when hashed again, the session would immediately changeUser() to the respective user. This legacy persistent login was completely removed. Instead, any sessions that belong to an authenticated user will automatically be long-lived. These long-lived sessions expire no sooner than 14 days after the last activity, ensuring that the user continously stays logged in, provided that they visit the page at least once per fortnight.","title":"Legacy Persistent Login"},{"location":"migration/wsc53/session/#multiple-sessions","text":"To allow for a proper separation of these long-lived user sessions, WoltLab Suite now allows for multiple sessions per user. These sessions are completely unrelated to each other. Specifically, they do not share session variables and they expire independently. As the existing wcf1_session table is also used for the online lists and location tracking, it will be maintained on a best effort basis. It no longer stores any private session data. The actual sessions storing security sensitive information are in an unrelated location. They must only be accessed via the PHP API exposed by the SessionHandler .","title":"Multiple Sessions"},{"location":"migration/wsc53/session/#merged-acp-and-frontend-sessions","text":"WoltLab Suite 5.4 shares a single session across both the frontend, as well as the ACP. When a user logs in to the frontend, they will also be logged into the ACP and vice versa. Actual access to the ACP is controlled via the new reauthentication mechanism . The session variable store is scoped: Session variables set within the frontend are not available within the ACP and vice versa.","title":"Merged ACP and Frontend Sessions"},{"location":"migration/wsc53/session/#improved-authentication-and-reauthentication","text":"WoltLab Suite 5.4 ships with multi-factor authentication support and a generic re-authentication implementation that can be used to verify the account owner\u2019s presence.","title":"Improved Authentication and Reauthentication"},{"location":"migration/wsc53/session/#additions-and-changes","text":"","title":"Additions and Changes"},{"location":"migration/wsc53/session/#password-hashing","text":"WoltLab Suite 5.4 includes a new object-oriented password hashing framework that is modeled after PHP\u2019s password_* API. Check PasswordAlgorithmManager and IPasswordAlgorithm for details. The new default password hash is a standard BCrypt hash. All newly generated hashes in wcf1_user.password will now include a type prefix, instead of just passwords imported from other systems.","title":"Password Hashing"},{"location":"migration/wsc53/session/#session-storage","text":"The wcf1_session table will no longer be used for session storage. Instead, it is maintained for compatibility with existing online lists. The actual session storage is considered an implementation detail and you must not directly interact with the session tables. Future versions might support alternative session backends, such as Redis. Do not interact directly with the session database tables but only via the SessionHandler class!","title":"Session Storage"},{"location":"migration/wsc53/session/#reauthentication","text":"For security sensitive processing, you might want to ensure that the account owner is actually present instead of a third party accessing a session that was accidentally left logged in. WoltLab Suite 5.4 ships with a generic reauthentication framework. To request reauthentication within your controller you need to: Use the wcf\\system\\user\\authentication\\TReauthenticationCheck trait. Call: $this -> requestReauthentication ( LinkHandler :: getInstance () -> getControllerLink ( static :: class , [ /* additional parameters */ ])); requestReauthentication() will check if the user has recently authenticated themselves. If they did, the request proceeds as usual. Otherwise, they will be asked to reauthenticate themselves. After the successful authentication, they will be redirected to the URL that was passed as the first parameter (the current controller within the example). Details can be found in WoltLab/WCF#3775 .","title":"Reauthentication"},{"location":"migration/wsc53/session/#multi-factor-authentication","text":"To implement multi-factor authentication securely, WoltLab Suite 5.4 implements the concept of a \u201cpending user change\u201d. The user will not be logged in (i.e. WCF::getUser()->userID returns null ) until they authenticate themselves with their second factor. Requesting multi-factor authentication is done on an opt-in basis for compatibility reasons. If you perform authentication yourself and do not trust the authentication source to perform multi-factor authentication itself, you will need to adjust your logic to request multi-factor authentication from WoltLab Suite: Previously: WCF :: getSession () -> changeUser ( $targetUser ); Now: $isPending = WCF :: getSession () -> changeUserAfterMultifactorAuthentication ( $targetUser ); if ( $isPending ) { // Redirect to the authentication form. The user will not be logged in. // Note: Do not use `getControllerLink` to support both the frontend as well as the ACP. HeaderUtil :: redirect ( LinkHandler :: getInstance () -> getLink ( 'MultifactorAuthentication' , [ 'url' => /* Return To */ , ])); exit ; } // Proceed as usual. The user will be logged in.","title":"Multi-factor Authentication"},{"location":"migration/wsc53/session/#adding-multi-factor-methods","text":"Adding your own multi-factor method requires the implementation of a single object type: <type> <name> com.example.multifactor.foobar </name> <definitionname> com.woltlab.wcf.multifactor </definitionname> <icon> <!-- Font Awesome 4 Icon Name goes here. --> </icon> <priority> <!-- Determines the sort order, higher priority will be preferred for authentication. --> </priority> <classname> wcf\\system\\user\\multifactor\\FoobarMultifactorMethod </classname> </type> The given classname must implement the IMultifactorMethod interface. As a self-contained example, you can find the initial implementation of the email multi-factor method in WoltLab/WCF#3729 . Please check the version history of the PHP class to make sure you do not miss important changes that were added later. Multi-factor authentication is security sensitive. Make sure to carefully read the remarks in IMultifactorMethod for possible issues. Also make sure to carefully test your implementation against all sorts of incorrect input and consider attack vectors such as race conditions. It is strongly recommended to generously check the current state by leveraging assertions and exceptions.","title":"Adding Multi-factor Methods"},{"location":"migration/wsc53/session/#deprecations-and-removals","text":"","title":"Deprecations and Removals"},{"location":"migration/wsc53/session/#sessionhandler","text":"Most of the changes with regard to the new session handling happened in SessionHandler . Most notably, SessionHandler now is marked final to ensure proper encapsulation of data. A number of methods in SessionHandler are now deprecated and result in a noop. This change mostly affects methods that have been used to bootstrap the session, such as setHasValidCookie() . Additionally, accessing the following keys on the session is deprecated. They directly map to an existing method in another class and any uses can easily be updated: - ipAddress - userAgent - requestURI - requestMethod - lastActivityTime Refer to the implementation for details.","title":"SessionHandler"},{"location":"migration/wsc53/session/#acp-sessions","text":"The database tables related to ACP sessions have been removed. The PHP classes have been preserved due to being used within the class hierarchy of the legacy sessions.","title":"ACP Sessions"},{"location":"migration/wsc53/session/#cookies","text":"The _userID , _password , _cookieHash and _cookieHash_acp cookies will no longer be created nor consumed.","title":"Cookies"},{"location":"migration/wsc53/session/#virtual-sessions","text":"The virtual session logic existed to support multiple devices per single session in wcf1_session . Virtual sessions are no longer required with the refactored session handling. Anything related to virtual sessions has been completely removed as they are considered an implementation detail. This removal includes PHP classes and database tables.","title":"Virtual Sessions"},{"location":"migration/wsc53/session/#security-token-constants","text":"The security token constants are deprecated. Instead, the methods of SessionHandler should be used (e.g. ->getSecurityToken() ). Within templates, you should migrate to the {csrfToken} tag in place of {@SECURITY_TOKEN_INPUT_TAG} . The {csrfToken} tag is a drop-in replacement and was backported to WoltLab Suite 5.2+, allowing you to maintain compatibility across a broad range of versions.","title":"Security Token Constants"},{"location":"migration/wsc53/session/#passwordutil-and-double-bcrypt-hashes","text":"Most of the methods in PasswordUtil are deprecated in favor of the new password hashing framework.","title":"PasswordUtil and Double BCrypt Hashes"},{"location":"migration/wsc53/templates/","text":"Migrating from WSC 5.3 - Templates and Languages # {csrfToken} # Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"Templates"},{"location":"migration/wsc53/templates/#migrating-from-wsc-53-templates-and-languages","text":"","title":"Migrating from WSC 5.3 - Templates and Languages"},{"location":"migration/wsc53/templates/#csrftoken","text":"Going forward, any uses of the SECURITY_TOKEN_* constants should be avoided. To reference the CSRF token (\u201cSecurity Token\u201d) within templates, the {csrfToken} template plugin was added. Before: { @ SECURITY_TOKEN_INPUT_TAG } { link controller = \"Foo\" } t= { @ SECURITY_TOKEN }{ /link } After: { csrfToken } { link controller = \"Foo\" } t= { csrfToken type = url }{ /link } { * The use of the CSRF token in URLs is discouraged. Modifications should happen by means of a POST request. * } The {csrfToken} plugin was backported to WoltLab Suite 5.2 and higher, allowing compatibility with a large range of WoltLab Suite branches. See WoltLab/WCF#3612 for details.","title":"{csrfToken}"},{"location":"package/database-php-api/","text":"Database PHP API # Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ]; Database Tables # There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop () Columns # To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull () Foreign Keys # Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data. Indices # Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Database PHP API"},{"location":"package/database-php-api/#database-php-api","text":"Available since WoltLab Suite 5.2. While the sql package installation plugin supports adding and removing tables, columns, and indices, it is not able to handle cases where the added table, column, or index already exist. We have added a new PHP-based API to manipulate the database scheme which can be used in combination with the script package installation plugin that skips parts that already exist: $tables = [ // list of `DatabaseTable` objects ]; ( new DatabaseTableChangeProcessor ( /** @var ScriptPackageInstallationPlugin $this */ $this -> installation -> getPackage (), $tables , WCF :: getDB () -> getEditor ()) ) -> process (); All of the relevant components can be found in the wcf\\system\\database\\table namespace. With WoltLab Suite 5.4, you should use the new database package installation plugin for which you only have to return the array of affected database tables: return [ // list of `DatabaseTable` objects ];","title":"Database PHP API"},{"location":"package/database-php-api/#database-tables","text":"There are two classes representing database tables: DatabaseTable and PartialDatabaseTable . If a new table should be created, use DatabaseTable . In all other cases, PartialDatabaseTable should be used as it provides an additional save-guard against accidentally creating a new table by having a typo in the table name: If the tables does not already exist, a table represented by PartialDatabaseTable will cause an exception (while a DatabaseTable table will simply be created). To create a table, a DatabaseTable object with the table's name as to be created and table's columns, foreign keys and indices have to be specified: DatabaseTable :: create ( 'foo1_bar' ) -> columns ([ // columns ]) -> foreignKeys ([ // foreign keys ]) -> indices ([ // indices ]) To update a table, the same code as above can be used, except for PartialDatabaseTable being used instead of DatabaseTable . To drop a table, only the drop() method has to be called: PartialDatabaseTable :: create ( 'foo1_bar' ) -> drop ()","title":"Database Tables"},{"location":"package/database-php-api/#columns","text":"To represent a column of a database table, you have to create an instance of the relevant column class found in the wcf\\system\\database\\table\\column namespace. Such instances are created similarly to database table objects using the create() factory method and passing the column name as the parameter. Every column type supports the following methods: defaultValue($defaultValue) sets the default value of the column (default: none). drop() to drop the column. notNull($notNull = true) sets if the value of the column can be NULL (default: false ). Depending on the specific column class implementing additional interfaces, the following methods are also available: IAutoIncrementDatabaseTableColumn::autoIncrement($autoIncrement = true) sets if the value of the colum is auto-incremented. IDecimalsDatabaseTableColumn::decimals($decimals) sets the number of decimals the column supports. IEnumDatabaseTableColumn::enumValues(array $values) sets the predetermined set of valid values of the column. ILengthDatabaseTableColumn::length($length) sets the (maximum) length of the column. Additionally, there are some additionally classes of commonly used columns with specific properties: DefaultFalseBooleanDatabaseTableColumn (a tinyint column with length 1 , default value 0 and whose values cannot be null ) DefaultTrueBooleanDatabaseTableColumn (a tinyint column with length 0 , default value 0 and whose values cannot be null ) NotNullInt10DatabaseTableColumn (a int column with length 10 and whose values cannot be null ) NotNullVarchar191DatabaseTableColumn (a varchar column with length 191 and whose values cannot be null ) NotNullVarchar255DatabaseTableColumn (a varchar column with length 255 and whose values cannot be null ) ObjectIdDatabaseTableColumn (a int column with length 10 , whose values cannot be null , and whose values are auto-incremented) Examples: DefaultFalseBooleanDatabaseTableColumn :: create ( 'isDisabled' ) NotNullInt10DatabaseTableColumn :: create ( 'fooTypeID' ) SmallintDatabaseTableColumn :: create ( 'bar' ) -> length ( 5 ) -> notNull ()","title":"Columns"},{"location":"package/database-php-api/#foreign-keys","text":"Foreign keys are represented by DatabaseTableForeignKey objects: DatabaseTableForeignKey :: create () -> columns ([ 'fooID' ]) -> referencedTable ( 'wcf1_foo' ) -> referencedColumns ([ 'fooID' ]) -> onDelete ( 'CASCADE' ) The supported actions for onDelete() and onUpdate() are CASCADE , NO ACTION , and SET NULL . To drop a foreign key, all of the relevant data to create the foreign key has to be present and the drop() method has to be called. DatabaseTableForeignKey::create() also supports the foreign key name as a parameter. If it is not present, DatabaseTable::foreignKeys() will automatically set one based on the foreign key's data.","title":"Foreign Keys"},{"location":"package/database-php-api/#indices","text":"Indices are represented by DatabaseTableIndex objects: DatabaseTableIndex :: create () -> type ( DatabaseTableIndex :: UNIQUE_TYPE ) -> columns ([ 'fooID' ]) There are four different types: DatabaseTableIndex::DEFAULT_TYPE (default), DatabaseTableIndex::PRIMARY_TYPE , DatabaseTableIndex::UNIQUE_TYPE , and DatabaseTableIndex::FULLTEXT_TYPE . For primary keys, there is also the DatabaseTablePrimaryIndex class which automatically sets the type to DatabaseTableIndex::PRIMARY_TYPE . To drop a index, all of the relevant data to create the index has to be present and the drop() method has to be called. DatabaseTableIndex::create() also supports the index name as a parameter. If it is not present, DatabaseTable::indices() will automatically set one based on the index data.","title":"Indices"},{"location":"package/package-xml/","text":"package.xml # The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package> Elements # <package> # The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ). <packageinformation> # Holds the entire meta data of the package. <packagename> # This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation> <packagedescription> # Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details. <version> # The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction) <date> # Must be a valid ISO 8601 date, e.g. 2013-12-27 . <authorinformation> # Holds meta data regarding the package's author. <author> # Can be anything you want. <authorurl> # (optional) URL to the author's website. <requiredpackages> # A list of packages including their version required for this package to work. <requiredpackage> # Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml . <optionalpackage> # A list of optional packages which can be selected by the user at the very end of the installation process. <optionalpackage> # Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml . <excludedpackages> # List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed. <excludedpackage> # Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher. <compatibility> # Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility> Existing API versions # WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a <instructions> # List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first. <instructions type=\"install\"> # List of instructions for a new installation of this package. <instructions type=\"update\" fromversion=\"\u2026\"> # The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 . <instruction> # Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" /> <void/> # Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"package.xml"},{"location":"package/package-xml/#packagexml","text":"The package.xml is the core component of every package. It provides the meta data (e.g. package name, description, author) and the instruction set for a new installation and/or updating from a previous version.","title":"package.xml"},{"location":"package/package-xml/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.example.package\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/package.xsd\" > <packageinformation> <packagename> Simple Package </packagename> <packagedescription> A simple package to demonstrate the package system of WoltLab Suite Core </packagedescription> <version> 1.0.0 </version> <date> 2016-12-18 </date> </packageinformation> <authorinformation> <author> YOUR NAME </author> <authorurl> http://www.example.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.0.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"6.0.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <instructions type= \"install\" > <instruction type= \"file\" /> <instruction type= \"template\" > templates.tar </instruction> </instructions> </package>","title":"Example"},{"location":"package/package-xml/#elements","text":"","title":"Elements"},{"location":"package/package-xml/#package","text":"The root node of every package.xml it contains the reference to the namespace and the location of the XML Schema Definition (XSD). The attribute name is the most important part, it holds the unique package identifier and is mandatory. It is based upon your domain name and the package name of your choice. For example WoltLab Suite Forum (formerly know an WoltLab Burning Board and usually abbreviated as wbb ) is created by WoltLab which owns the domain woltlab.com . The resulting package identifier is com.woltlab.wbb ( <tld>.<domain>.<packageName> ).","title":"&lt;package&gt;"},{"location":"package/package-xml/#packageinformation","text":"Holds the entire meta data of the package.","title":"&lt;packageinformation&gt;"},{"location":"package/package-xml/#packagename","text":"This is the actual package name displayed to the end user, this can be anything you want, try to keep it short. It supports the attribute languagecode which allows you to provide the package name in different languages, please be aware that if it is not present, en (English) is assumed: <packageinformation> <packagename> Simple Package </packagename> <packagename languagecode= \"de\" > Einfaches Paket </packagename> </packageinformation>","title":"&lt;packagename&gt;"},{"location":"package/package-xml/#packagedescription","text":"Brief summary of the package, use it to explain what it does since the package name might not always be clear enough. The attribute languagecode is available here too, please reference to <packagename> for details.","title":"&lt;packagedescription&gt;"},{"location":"package/package-xml/#version","text":"The package's version number, this is a string consisting of three numbers separated with a dot and optionally followed by a keyword (must be followed with another number). The possible keywords are: Alpha/dev (both is regarded to be the same) Beta RC (release candidate) pl (patch level) Valid examples: 1.0.0 1.12.13 Alpha 19 7.0.0 pl 3 Invalid examples: 1.0.0 Beta (keyword Beta must be followed by a number) 2.0 RC 3 (version number must consists of 3 blocks of numbers) 1.2.3 dev 4.5 (4.5 is not an integer, 4 or 5 would be valid but not the fraction)","title":"&lt;version&gt;"},{"location":"package/package-xml/#date","text":"Must be a valid ISO 8601 date, e.g. 2013-12-27 .","title":"&lt;date&gt;"},{"location":"package/package-xml/#authorinformation","text":"Holds meta data regarding the package's author.","title":"&lt;authorinformation&gt;"},{"location":"package/package-xml/#author","text":"Can be anything you want.","title":"&lt;author&gt;"},{"location":"package/package-xml/#authorurl","text":"(optional) URL to the author's website.","title":"&lt;authorurl&gt;"},{"location":"package/package-xml/#requiredpackages","text":"A list of packages including their version required for this package to work.","title":"&lt;requiredpackages&gt;"},{"location":"package/package-xml/#requiredpackage","text":"Example: <requiredpackage minversion= \"2.0.0\" file= \"requirements/com.woltlab.wcf.tar\" > com.woltlab.wcf </requiredpackage> The attribute minversion must be a valid version number as described in <version> . The file attribute is optional and specifies the location of the required package's archive relative to the package.xml .","title":"&lt;requiredpackage&gt;"},{"location":"package/package-xml/#optionalpackage","text":"A list of optional packages which can be selected by the user at the very end of the installation process.","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#optionalpackage_1","text":"Example: <optionalpackage file= \"optionals/com.woltlab.wcf.moderatedUserGroup.tar\" > com.woltlab.wcf.moderatedUserGroup </optionalpackage> The file attribute specifies the location of the optional package's archive relative to the package.xml .","title":"&lt;optionalpackage&gt;"},{"location":"package/package-xml/#excludedpackages","text":"List of packages which conflict with this package. It is not possible to install it if any of the specified packages is installed. In return you cannot install an excluded package if this package is installed.","title":"&lt;excludedpackages&gt;"},{"location":"package/package-xml/#excludedpackage","text":"Example: <excludedpackage version= \"3.1.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> The attribute version must be a valid version number as described in the \\<version> section. In the example above it will be impossible to install this package in WoltLab Suite Core 3.1.0 Alpha 1 or higher.","title":"&lt;excludedpackage&gt;"},{"location":"package/package-xml/#compatibility","text":"Available since WoltLab Suite 3.1 With the release of WoltLab Suite 5.2 the API versions were abolished. Instead of using API versions packages should exclude version 6.0.0 Alpha 1 of com.woltlab.wcf going forward. WoltLab Suite 3.1 introduced a new versioning system that focused around the API compatibility and is intended to replace the <excludedpackage> instruction for the Core for most plugins. The <compatibility> -tag holds a list of compatible API versions, and while only a single version is available at the time of writing, future versions will add more versions with backwards-compatibility in mind. Example: <compatibility> <api version= \"2018\" /> </compatibility>","title":"&lt;compatibility&gt;"},{"location":"package/package-xml/#existing-api-versions","text":"WoltLab Suite Core API-Version Backwards-Compatible to API-Version 3.1 2018 n/a","title":"Existing API versions"},{"location":"package/package-xml/#instructions","text":"List of instructions to be executed upon install or update. The order is important, the topmost <instruction> will be executed first.","title":"&lt;instructions&gt;"},{"location":"package/package-xml/#instructions-typeinstall","text":"List of instructions for a new installation of this package.","title":"&lt;instructions type=\"install\"&gt;"},{"location":"package/package-xml/#instructions-typeupdate-fromversion","text":"The attribute fromversion must be a valid version number as described in the \\<version> section and specifies a possible update from that very version to the package's version. The installation process will pick exactly one update instruction, ignoring everything else. Please read the explanation below! Example: Installed version: 1.0.0 Package version: 1.0.2 <instructions type= \"update\" fromversion= \"1.0.0\" > <!-- \u2026 --> </instructions> <instructions type= \"update\" fromversion= \"1.0.1\" > <!-- \u2026 --> </instructions> In this example WoltLab Suite Core will pick the first update block since it allows an update from 1.0.0 -> 1.0.2 . The other block is not considered, since the currently installed version is 1.0.0 . After applying the update block ( fromversion=\"1.0.0\" ), the version now reads 1.0.2 .","title":"&lt;instructions type=\"update\" fromversion=\"\u2026\"&gt;"},{"location":"package/package-xml/#instruction","text":"Example: <instruction type= \"objectTypeDefinition\" > objectTypeDefinition.xml </instruction> The attribute type specifies the instruction type which is used to determine the package installation plugin (PIP) invoked to handle its value. The value must be a valid file relative to the location of package.xml . Many PIPs provide default file names which are used if no value is given: <instruction type= \"objectTypeDefinition\" /> There is a list of all default PIPs available. Both the type -attribute and the element value are case-sensitive. Windows does not care if the file is called objecttypedefinition.xml but was referenced as objectTypeDefinition.xml , but both Linux and Mac systems will be unable to find the file. In addition to the type attribute, an optional run attribute (with standalone as the only valid value) is supported which forces the installation to execute this PIP in an isolated request, allowing a single, resource-heavy PIP to execute without encountering restrictions such as PHP\u2019s memory_limit or max_execution_time : <instruction type= \"file\" run= \"standalone\" />","title":"&lt;instruction&gt;"},{"location":"package/package-xml/#void","text":"Sometimes a package update should only adjust the metadata of the package, for example, an optional package was added. However, WoltLab Suite Core requires that the list of <instructions> is non-empty. Instead of using a dummy <instruction> that idempotently updates some PIP, the <void/> tag can be used for this use-case. Using the <void/> tag is only valid for <instructions type=\"update\"> and must not be accompanied by other <instruction> tags. Example: <instructions type= \"update\" fromversion= \"1.0.0\" > <void/> </instructions>","title":"&lt;void/&gt;"},{"location":"package/pip/","text":"Package Installation Plugins # Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary. Built-In PIPs # Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Overview"},{"location":"package/pip/#package-installation-plugins","text":"Package Installation Plugins (PIPs) are interfaces to deploy and edit content as well as components. For XML-based PIPs: <![CDATA[]]> must be used for language items and page contents. In all other cases it may only be used when necessary.","title":"Package Installation Plugins"},{"location":"package/pip/#built-in-pips","text":"Name Description aclOption Customizable permissions for individual objects acpMenu Admin panel menu categories and items acpSearchProvider Data provider for the admin panel search acpTemplate Admin panel templates bbcode BBCodes for rich message formatting box Boxes that can be placed anywhere on a page clipboardAction Perform bulk operations on marked objects coreObject Access Singletons from within the template cronjob Periodically execute code with customizable intervals database Updates the database layout using the PHP API eventListener Register listeners for the event system file Deploy any type of files with the exception of templates language Language items mediaProvider Detect and convert links to media providers menu Side-wide and custom per-page menus menuItem Menu items for menus created through the menu PIP objectType Flexible type registry based on definitions objectTypeDefinition Groups objects and classes by functionality option Side-wide configuration options page Register page controllers and text-based pages pip Package Installation Plugins script Execute arbitrary PHP code during installation, update and uninstallation smiley Smileys sql Execute SQL instructions using a MySQL-flavored syntax (also see database PHP API ) style Style template Frontend templates templateListener Embed template code into templates without altering the original userGroupOption Permissions for user groups userMenu User menu categories and items userNotificationEvent Events of the user notification system userOption User settings userProfileMenu User profile tabs","title":"Built-In PIPs"},{"location":"package/pip/acl-option/","text":"ACL Option Package Installation Plugin # Add customizable permissions for individual objects. Option Components # Each acl option is described as an <option> element with the mandatory attribute name . <categoryname> # Optional The name of the acl option category to which the option belongs. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Category Components # Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value. <objecttype> # The name of the acl object type (of the object type definition com.woltlab.wcf.acl ). Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"aclOption"},{"location":"package/pip/acl-option/#acl-option-package-installation-plugin","text":"Add customizable permissions for individual objects.","title":"ACL Option Package Installation Plugin"},{"location":"package/pip/acl-option/#option-components","text":"Each acl option is described as an <option> element with the mandatory attribute name .","title":"Option Components"},{"location":"package/pip/acl-option/#categoryname","text":"Optional The name of the acl option category to which the option belongs.","title":"&lt;categoryname&gt;"},{"location":"package/pip/acl-option/#objecttype","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#category-components","text":"Each acl option category is described as an <category> element with the mandatory attribute name that should follow the naming pattern <permissionName> or <permissionType>.<permissionName> , with <permissionType> generally having user or mod as value.","title":"Category Components"},{"location":"package/pip/acl-option/#objecttype_1","text":"The name of the acl object type (of the object type definition com.woltlab.wcf.acl ).","title":"&lt;objecttype&gt;"},{"location":"package/pip/acl-option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/aclOption.xsd\" > <import> <categories> <category name= \"user.example\" > <objecttype> com.example.wcf.example </objecttype> </category> <category name= \"mod.example\" > <objecttype> com.example.wcf.example </objecttype> </category> </categories> <options> <option name= \"canAddExample\" > <categoryname> user.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> <option name= \"canDeleteExample\" > <categoryname> mod.example </categoryname> <objecttype> com.example.wcf.example </objecttype> </option> </options> </import> <delete> <optioncategory name= \"old.example\" > <objecttype> com.example.wcf.example </objecttype> </optioncategory> <option name= \"canDoSomethingWithExample\" > <objecttype> com.example.wcf.example </objecttype> </option> </delete> </data>","title":"Example"},{"location":"package/pip/acp-menu/","text":"ACP Menu Package Installation Plugin # Registers new ACP menu items. Components # Each item is described as an <acpmenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <icon> # Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"acpMenu"},{"location":"package/pip/acp-menu/#acp-menu-package-installation-plugin","text":"Registers new ACP menu items.","title":"ACP Menu Package Installation Plugin"},{"location":"package/pip/acp-menu/#components","text":"Each item is described as an <acpmenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/acp-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/acp-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/acp-menu/#icon","text":"Use an icon only for top-level and 4th-level items. Name of the Font Awesome icon class.","title":"&lt;icon&gt;"},{"location":"package/pip/acp-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/acp-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/acp-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpMenu.xsd\" > <import> <acpmenuitem name= \"foo.acp.menu.link.example\" > <parent> wcf.acp.menu.link.application </parent> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.list\" > <controller> foo\\acp\\page\\ExampleListPage </controller> <parent> foo.acp.menu.link.example </parent> <permissions> admin.foo.canManageExample </permissions> <showorder> 1 </showorder> </acpmenuitem> <acpmenuitem name= \"foo.acp.menu.link.example.add\" > <controller> foo\\acp\\form\\ExampleAddForm </controller> <parent> foo.acp.menu.link.example.list </parent> <permissions> admin.foo.canManageExample </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data>","title":"Example"},{"location":"package/pip/acp-search-provider/","text":"ACP Search Provider Package Installation Plugin # Registers data provider for the admin panel search. Components # Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name . <classname> # The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface. <showorder> # Optional Determines at which position of the search result list the provided results are shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"acpSearchProvider"},{"location":"package/pip/acp-search-provider/#acp-search-provider-package-installation-plugin","text":"Registers data provider for the admin panel search.","title":"ACP Search Provider Package Installation Plugin"},{"location":"package/pip/acp-search-provider/#components","text":"Each acp search result provider is described as an <acpsearchprovider> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/acp-search-provider/#classname","text":"The name of the class providing the search results, the class has to implement the wcf\\system\\search\\acp\\IACPSearchResultProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/acp-search-provider/#showorder","text":"Optional Determines at which position of the search result list the provided results are shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/acp-search-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/acpSearchProvider.xsd\" > <import> <acpsearchprovider name= \"com.woltlab.wcf.example\" > <classname> wcf\\system\\search\\acp\\ExampleACPSearchResultProvider </classname> <showorder> 1 </showorder> </acpsearchprovider> </import> </data>","title":"Example"},{"location":"package/pip/acp-template/","text":"ACP Template Installation Plugin # Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages. Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file. Attributes # application # The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory. Example in package.xml # <instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"acpTemplate"},{"location":"package/pip/acp-template/#acp-template-installation-plugin","text":"Add templates for acp pages and forms by providing an archive containing the template files. You cannot overwrite acp templates provided by other packages.","title":"ACP Template Installation Plugin"},{"location":"package/pip/acp-template/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The templates must all be in the root of the archive. Do not include any directories in the archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/acp-template/#attributes","text":"","title":"Attributes"},{"location":"package/pip/acp-template/#application","text":"The application attribute determines to which application the installed acp templates belong and thus in which directory the templates are installed. The value of the application attribute has to be the abbreviation of an installed application. If no application attribute is given, the following rules are applied: If the package installing the acp templates is an application, then the templates will be installed in this application's directory. If the package installing the acp templates is no application, then the templates will be installed in WoltLab Suite Core's directory.","title":"application"},{"location":"package/pip/acp-template/#example-in-packagexml","text":"<instruction type= \"acpTemplate\" /> <!-- is the same as --> <instruction type= \"acpTemplate\" > acptemplates.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"acpTemplate\" /> <instruction type= \"acpTemplate\" application= \"example\" />","title":"Example in package.xml"},{"location":"package/pip/bbcode/","text":"BBCode Package Installation Plugin # Registers new BBCodes. Components # Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user. <htmlopen> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode. <htmlclose> # Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags. <classname> # The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered. <wysiwygicon> # Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar. <buttonlabel> # Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon. <sourcecode> # Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead. <isBlockElement> # Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification). <attributes> # Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer. <html> # Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value. <validationpattern> # Optional Defines a regular expression that is used to validate the value of the attribute. <required> # Optional Specifies whether this attribute must be provided. <usetext> # Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"bbcode"},{"location":"package/pip/bbcode/#bbcode-package-installation-plugin","text":"Registers new BBCodes.","title":"BBCode Package Installation Plugin"},{"location":"package/pip/bbcode/#components","text":"Each bbcode is described as an <bbcode> element with the mandatory attribute name . The name attribute must contain alphanumeric characters only and is exposed to the user.","title":"Components"},{"location":"package/pip/bbcode/#htmlopen","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are literally copied into the opening tag of the bbcode.","title":"&lt;htmlopen&gt;"},{"location":"package/pip/bbcode/#htmlclose","text":"Optional: Must not be provided if <htmlopen> is not given. Must match the <htmlopen> tag. Do not provide for self-closing tags.","title":"&lt;htmlclose&gt;"},{"location":"package/pip/bbcode/#classname","text":"The name of the class providing the bbcode output, the class has to implement the wcf\\system\\bbcode\\IBBCode interface. BBCodes can be statically converted to HTML during input processing using a wcf\\system\\html\\metacode\\converter\\*MetaConverter class. This class does not need to be registered.","title":"&lt;classname&gt;"},{"location":"package/pip/bbcode/#wysiwygicon","text":"Optional Name of the Font Awesome icon class or path to a gif , jpg , jpeg , png , or svg image (placed inside the icon/ directory) to show in the editor toolbar.","title":"&lt;wysiwygicon&gt;"},{"location":"package/pip/bbcode/#buttonlabel","text":"Optional: Must be provided if an icon is given. Explanatory text to show when hovering the icon.","title":"&lt;buttonlabel&gt;"},{"location":"package/pip/bbcode/#sourcecode","text":"Do not set this to 1 if you don't specify a PHP class for processing. You must perform XSS sanitizing yourself! If set to 1 contents of this BBCode will not be interpreted, but literally passed through instead.","title":"&lt;sourcecode&gt;"},{"location":"package/pip/bbcode/#isblockelement","text":"Set to 1 if the output of this BBCode is a HTML block element (according to the HTML specification).","title":"&lt;isBlockElement&gt;"},{"location":"package/pip/bbcode/#attributes","text":"Each bbcode is described as an <attribute> element with the mandatory attribute name . The name attribute is a 0-indexed integer.","title":"&lt;attributes&gt;"},{"location":"package/pip/bbcode/#html","text":"Optional: Must not be provided if the BBCode is being processed a PHP class ( <classname> ). The contents of this tag are copied into the opening tag of the bbcode. %s is replaced by the attribute value.","title":"&lt;html&gt;"},{"location":"package/pip/bbcode/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of the attribute.","title":"&lt;validationpattern&gt;"},{"location":"package/pip/bbcode/#required","text":"Optional Specifies whether this attribute must be provided.","title":"&lt;required&gt;"},{"location":"package/pip/bbcode/#usetext","text":"Optional Should only be set to 1 for the attribute with name 0 . Specifies whether the text content of the BBCode should become this attribute's value.","title":"&lt;usetext&gt;"},{"location":"package/pip/bbcode/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns=\"http://www.woltlab.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.woltlab.com http://www.woltlab.com/XSD/2019/bbcode.xsd\"> <import> <bbcode name=\"foo\"> <classname>wcf\\system\\bbcode\\FooBBCode</classname> <attributes> <attribute name=\"0\"> <validationpattern>^\\d+$</validationpattern> <required>1</required> </attribute> </attributes> </bbcode> <bbcode name=\"example\"> <htmlopen>div</htmlopen> <htmlclose>div</htmlclose> <isBlockElement>1</isBlockElement> <wysiwygicon>fa-bath</wysiwygicon> <buttonlabel>wcf.editor.button.example</buttonlabel> </bbcode> </import> </data>","title":"Example"},{"location":"package/pip/box/","text":"Box Package Installation Plugin # Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based. Components # Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity . <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <boxType> # system # The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <objectType> # Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController . <position> # The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top Placeholder Positions # --8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\" <showHeader> # Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 . <visibleEverywhere> # Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> . <visibilityExceptions> # Inverts the <visibleEverywhere> setting for the listed pages only. <cssClassName> # Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the box title shown to the end users. <content> # The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"box"},{"location":"package/pip/box/#box-package-installation-plugin","text":"Deploy and manage boxes that can be placed anywhere on the site, they come in two flavors: system and content-based.","title":"Box Package Installation Plugin"},{"location":"package/pip/box/#components","text":"Each item is described as a <box> element with the mandatory attribute name that should follow the naming pattern <packageIdentifier>.<BoxName> , e.g. com.woltlab.wcf.RecentActivity .","title":"Components"},{"location":"package/pip/box/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/box/#boxtype","text":"","title":"&lt;boxType&gt;"},{"location":"package/pip/box/#system","text":"The special system type is reserved for boxes that pull their properties and content from a registered PHP class. Requires the <objectType> element.","title":"system"},{"location":"package/pip/box/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/box/#objecttype","text":"Required for boxes with boxType = system , must be registered through the objectType PIP for the definition com.woltlab.wcf.boxController .","title":"&lt;objectType&gt;"},{"location":"package/pip/box/#position","text":"The default display position of this box, can be any of the following: bottom contentBottom contentTop footer footerBoxes headerBoxes hero sidebarLeft sidebarRight top","title":"&lt;position&gt;"},{"location":"package/pip/box/#placeholder-positions","text":"--8<-- \"image.html file=\"boxPlaceholders.png\" alt=\"Visual illustration of placeholder positions\"\"","title":"Placeholder Positions"},{"location":"package/pip/box/#showheader","text":"Setting this to 0 will suppress display of the box title, useful for boxes containing advertisements or similar. Defaults to 1 .","title":"&lt;showHeader&gt;"},{"location":"package/pip/box/#visibleeverywhere","text":"Controls the display on all pages ( 1 ) or none ( 0 ), can be used in conjunction with <visibilityExceptions> .","title":"&lt;visibleEverywhere&gt;"},{"location":"package/pip/box/#visibilityexceptions","text":"Inverts the <visibleEverywhere> setting for the listed pages only.","title":"&lt;visibilityExceptions&gt;"},{"location":"package/pip/box/#cssclassname","text":"Provide a custom CSS class name that is added to the menu container, allowing further customization of the menu's appearance.","title":"&lt;cssClassName&gt;"},{"location":"package/pip/box/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/box/#title","text":"The title element is required and controls the box title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/box/#content_1","text":"The content that should be used to populate the box, only used and required if the boxType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/box/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/box.xsd\" > <import> <box identifier= \"com.woltlab.wcf.RecentActivity\" > <name language= \"de\" > Letzte Aktivit\u00e4ten </name> <name language= \"en\" > Recent Activities </name> <boxType> system </boxType> <objectType> com.woltlab.wcf.recentActivityList </objectType> <position> contentBottom </position> <showHeader> 0 </showHeader> <visibleEverywhere> 0 </visibleEverywhere> <visibilityExceptions> <page> com.woltlab.wcf.Dashboard </page> </visibilityExceptions> <limit> 10 </limit> <content language= \"de\" > <title> Letzte Aktivit\u00e4ten </title> </content> <content language= \"en\" > <title> Recent Activities </title> </content> </box> </import> <delete> <box identifier= \"com.woltlab.wcf.RecentActivity\" /> </delete> </data>","title":"Example"},{"location":"package/pip/clipboard-action/","text":"Clipboard Action Package Installation Plugin # Registers clipboard actions. Components # Each clipboard action is described as an <action> element with the mandatory attribute name . <actionclassname> # The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction . <pages> # Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available. <showorder> # Optional Determines at which position of the clipboard action list the action is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"clipboardAction"},{"location":"package/pip/clipboard-action/#clipboard-action-package-installation-plugin","text":"Registers clipboard actions.","title":"Clipboard Action Package Installation Plugin"},{"location":"package/pip/clipboard-action/#components","text":"Each clipboard action is described as an <action> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/clipboard-action/#actionclassname","text":"The name of the class used by the clipboard API to process the concrete action. The class has to implement the wcf\\system\\clipboard\\action\\IClipboardAction interface, best by extending wcf\\system\\clipboard\\action\\AbstractClipboardAction .","title":"&lt;actionclassname&gt;"},{"location":"package/pip/clipboard-action/#pages","text":"Element with <page> children whose value contains the class name of the controller of the page on which the clipboard action is available.","title":"&lt;pages&gt;"},{"location":"package/pip/clipboard-action/#showorder","text":"Optional Determines at which position of the clipboard action list the action is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/clipboard-action/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/clipboardAction.xsd\" > <import> <action name= \"delete\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 1 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"foo\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 2 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> <action name= \"bar\" > <actionclassname> wcf\\system\\clipboard\\action\\ExampleClipboardAction </actionclassname> <showorder> 3 </showorder> <pages> <page> wcf\\acp\\page\\ExampleListPage </page> </pages> </action> </import> </data>","title":"Example"},{"location":"package/pip/core-object/","text":"Core Object Package Installation Plugin # Registers wcf\\system\\SingletonFactory objects to be accessible in templates. Components # Each item is described as a <coreobject> element with the mandatory element objectname . <objectname> # The fully qualified class name of the class. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"coreObject"},{"location":"package/pip/core-object/#core-object-package-installation-plugin","text":"Registers wcf\\system\\SingletonFactory objects to be accessible in templates.","title":"Core Object Package Installation Plugin"},{"location":"package/pip/core-object/#components","text":"Each item is described as a <coreobject> element with the mandatory element objectname .","title":"Components"},{"location":"package/pip/core-object/#objectname","text":"The fully qualified class name of the class.","title":"&lt;objectname&gt;"},{"location":"package/pip/core-object/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/coreObject.xsd\" > <import> <coreobject> <objectname> wcf\\system\\example\\ExampleHandler </objectname> </coreobject> </import> </data> This object can be accessed in templates via $__wcf->getExampleHandler() (in general: the method name begins with get and ends with the unqualified class name).","title":"Example"},{"location":"package/pip/cronjob/","text":"Cronjob Package Installation Plugin # Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past. Components # Each cronjob is described as an <cronjob> element with the mandatory attribute name . <classname> # The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface. <description> # The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator. <start*> # All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax. <canBeEdited> # Controls whether the administrator may edit the fields of the cronjob. <canBeDisabled> # Controls whether the administrator may disable the cronjob. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"cronjob"},{"location":"package/pip/cronjob/#cronjob-package-installation-plugin","text":"Registers new cronjobs. The cronjob schedular works similar to the cron(8) daemon, which might not available to web applications on regular webspaces. The main difference is that WoltLab Suite\u2019s cronjobs do not guarantee execution at the specified points in time: WoltLab Suite\u2019s cronjobs are triggered by regular visitors in an AJAX request, once the next execution point lies in the past.","title":"Cronjob Package Installation Plugin"},{"location":"package/pip/cronjob/#components","text":"Each cronjob is described as an <cronjob> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/cronjob/#classname","text":"The name of the class providing the cronjob's behaviour, the class has to implement the wcf\\system\\cronjob\\ICronjob interface.","title":"&lt;classname&gt;"},{"location":"package/pip/cronjob/#description","text":"The language attribute is optional and should specify the ISO-639-1 language code. Provides a human readable description for the administrator.","title":"&lt;description&gt;"},{"location":"package/pip/cronjob/#start","text":"All of the five startMinute , startHour , startDom (Day Of Month), startMonth , startDow (Day Of Week) are required. They correspond to the fields in crontab(5) of a cron daemon and accept the same syntax.","title":"&lt;start*&gt;"},{"location":"package/pip/cronjob/#canbeedited","text":"Controls whether the administrator may edit the fields of the cronjob.","title":"&lt;canBeEdited&gt;"},{"location":"package/pip/cronjob/#canbedisabled","text":"Controls whether the administrator may disable the cronjob.","title":"&lt;canBeDisabled&gt;"},{"location":"package/pip/cronjob/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/cronjob/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/cronjob.xsd\" > <import> <cronjob name= \"com.example.package.example\" > <classname> wcf\\system\\cronjob\\ExampleCronjob </classname> <description> Serves as an example </description> <description language= \"de\" > Stellt ein Beispiel dar </description> <startminute> 0 </startminute> <starthour> 2 </starthour> <startdom> */2 </startdom> <startmonth> * </startmonth> <startdow> * </startdow> <canbeedited> 1 </canbeedited> <canbedisabled> 1 </canbedisabled> </cronjob> </import> </data>","title":"Example"},{"location":"package/pip/database/","text":"Database Package Installation Plugin # Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename. Execution environment # The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#database-package-installation-plugin","text":"Available since WoltLab Suite 5.4. Update the database layout using the PHP API . You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Database Package Installation Plugin"},{"location":"package/pip/database/#attributes","text":"","title":"Attributes"},{"location":"package/pip/database/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/database/#expected-value","text":"The database -PIP expects a relative path to a .php file that returns an array of DatabaseTable objects.","title":"Expected value"},{"location":"package/pip/database/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: acp/database/install_<package>_<version>.php (example: acp/database/install_com.woltlab.wbb_5.4.0.php ) Update: acp/database/update_<package>_<targetVersion>.php (example: acp/database/update_com.woltlab.wbb_5.4.1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . If you run multiple update scripts, you can append additional information in the filename.","title":"Naming convention"},{"location":"package/pip/database/#execution-environment","text":"The script is included using include() within DatabasePackageInstallationPlugin::updateDatabase() .","title":"Execution environment"},{"location":"package/pip/event-listener/","text":"Event Listener Package Installation Plugin # Registers event listeners. An explanation of events and event listeners can be found here . Components # Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <eventclassname> # The event class name is the name of the class in which the event is fired. <eventname> # The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events. <listenerclassname> # The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface! <inherit> # The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value. <environment> # The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ). <nice> # The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed. <permissions> # The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"eventListener"},{"location":"package/pip/event-listener/#event-listener-package-installation-plugin","text":"Registers event listeners. An explanation of events and event listeners can be found here .","title":"Event Listener Package Installation Plugin"},{"location":"package/pip/event-listener/#components","text":"Each event listener is described as an <eventlistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/event-listener/#eventclassname","text":"The event class name is the name of the class in which the event is fired.","title":"&lt;eventclassname&gt;"},{"location":"package/pip/event-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same class. You can either give a single event name or a comma-separated list of event names in which case the event listener listens to all of the listed events.","title":"&lt;eventname&gt;"},{"location":"package/pip/event-listener/#listenerclassname","text":"The listener class name is the name of the class which is triggered if the relevant event is fired. The PHP class has to implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface. Legacy event listeners are only required to implement the deprecated wcf\\system\\event\\IEventListener interface. When writing new code or update existing code, you should always implement the wcf\\system\\event\\listener\\IParameterizedEventListener interface!","title":"&lt;listenerclassname&gt;"},{"location":"package/pip/event-listener/#inherit","text":"The inherit value can either be 0 (default value if the element is omitted) or 1 and determines if the event listener is also triggered for child classes of the given event class name. This is the case if 1 is used as the value.","title":"&lt;inherit&gt;"},{"location":"package/pip/event-listener/#environment","text":"The value of the environment element must be one of user , admin or all and defaults to user if no value is given. The value determines if the event listener will be executed in the frontend ( user ), the backend ( admin ) or both ( all ).","title":"&lt;environment&gt;"},{"location":"package/pip/event-listener/#nice","text":"The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of event listeners. Event listeners with smaller nice values are executed first. If the nice value of two event listeners is equal, they are sorted by the listener class name. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/event-listener/#options","text":"The options element can contain a comma-separated list of options of which at least one needs to be enabled for the event listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/event-listener/#permissions","text":"The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the event listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/event-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"inheritedAdminExample\" > <eventclassname> wcf\\acp\\form\\UserAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\InheritedAdminExampleListener </listenerclassname> <inherit> 1 </inherit> <environment> admin </environment> </eventlistener> <eventlistener name= \"nonInheritedUserExample\" > <eventclassname> wcf\\form\\SettingsForm </eventclassname> <eventname> assignVariables </eventname> <listenerclassname> wcf\\system\\event\\listener\\NonInheritedUserExampleListener </listenerclassname> </eventlistener> </import> <delete> <eventlistener name= \"oldEventListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/file/","text":"File Package Installation Plugin # Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates . Archive # The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file. Example in package.xml # <instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"file"},{"location":"package/pip/file/#file-package-installation-plugin","text":"Adds any type of files with the exception of templates. You cannot overwrite files provided by other packages. The application attribute behaves like it does for acp templates .","title":"File Package Installation Plugin"},{"location":"package/pip/file/#archive","text":"The acpTemplate package installation plugins expects a .tar (recommended) or .tar.gz archive. The file path given in the instruction element as its value must be relative to the package.xml file.","title":"Archive"},{"location":"package/pip/file/#example-in-packagexml","text":"<instruction type= \"file\" /> <!-- is the same as --> <instruction type= \"file\" > files.tar </instruction> <!-- if an application \"com.woltlab.example\" is being installed, the following lines are equivalent --> <instruction type= \"file\" /> <instruction type= \"file\" application= \"example\" /> <!-- if the same application wants to install additional files, in WoltLab Suite Core's directory: --> <instruction type= \"file\" application= \"wcf\" > files_wcf.tar </instruction>","title":"Example in package.xml"},{"location":"package/pip/language/","text":"Language Package Installation Plugin # Registers new language items. Components # The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute. <category> # Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). <item> # Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"language"},{"location":"package/pip/language/#language-package-installation-plugin","text":"Registers new language items.","title":"Language Package Installation Plugin"},{"location":"package/pip/language/#components","text":"The languagecode attribute is required and should specify the ISO-639-1 language code. The top level <language> node must contain a languagecode attribute.","title":"Components"},{"location":"package/pip/language/#category","text":"Each category must contain a name attribute containing two or three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E).","title":"&lt;category&gt;"},{"location":"package/pip/language/#item","text":"Each language item must contain a name attribute containing at least three components consisting of alphanumeric character only, separated by a single full stop ( . , U+002E). The name of the parent <category> node followed by a full stop must be a prefix of the <item> \u2019s name . Wrap the text content inside a CDATA to avoid escaping of special characters. Do not use the {lang} tag inside a language item. The text content of the <item> node is the value of the language item. Language items that are not in the wcf.global category support template scripting.","title":"&lt;item&gt;"},{"location":"package/pip/language/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <language xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/language.xsd\" languagecode= \"de\" > <category name= \"wcf.example\" > <item name= \"wcf.example.foo\" > <![CDATA[<strong>Look!</strong>]]> </item> </category> </language>","title":"Example"},{"location":"package/pip/media-provider/","text":"Media Provider Package Installation Plugin # Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages. Components # Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist . <title> # The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users. <regex> # The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group. <className> # <className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider . <html> # <className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"mediaProvider"},{"location":"package/pip/media-provider/#media-provider-package-installation-plugin","text":"Available since WoltLab Suite 3.1 Media providers are responsible to detect and convert links to a 3rd party service inside messages.","title":"Media Provider Package Installation Plugin"},{"location":"package/pip/media-provider/#components","text":"Each item is described as a <provider> element with the mandatory attribute name that should equal the lower-cased provider name. If a provider provides multiple components that are (largely) unrelated to each other, it is recommended to use a dash to separate the name and the component, e. g. youtube-playlist .","title":"Components"},{"location":"package/pip/media-provider/#title","text":"The title is displayed in the administration control panel and is only used there, the value is neither localizable nor is it ever exposed to regular users.","title":"&lt;title&gt;"},{"location":"package/pip/media-provider/#regex","text":"The regular expression used to identify links to this provider, it must not contain anchors or delimiters. It is strongly recommended to capture the primary object id using the (?P<ID>...) group.","title":"&lt;regex&gt;"},{"location":"package/pip/media-provider/#classname","text":"<className> and <html> are mutually exclusive. PHP-Callback-Class that is invoked to process the matched link in case that additional logic must be applied that cannot be handled through a simple replacement as defined by the <html> element. The callback-class must implement the interface \\wcf\\system\\bbcode\\media\\provider\\IBBCodeMediaProvider .","title":"&lt;className&gt;"},{"location":"package/pip/media-provider/#html","text":"<className> and <html> are mutually exclusive. Replacement HTML that gets populated using the captured matches in <regex> , variables are accessed as {$VariableName} . For example, the capture group (?P<ID>...) is accessed using {$ID} .","title":"&lt;html&gt;"},{"location":"package/pip/media-provider/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/mediaProvider.xsd\" > <import> <provider name= \"youtube\" > <title> YouTube </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/(?:#/)?watch\\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\\?|&)t=(?P<start>[0-9hms]+)$)?]]> </regex> <!-- advanced PHP callback --> <className> <![CDATA[wcf\\system\\bbcode\\media\\provider\\YouTubeBBCodeMediaProvider]]> </className> </provider> <provider name= \"youtube-playlist\" > <title> YouTube Playlist </title> <regex> <![CDATA[https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/)playlist\\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]> </regex> <!-- uses a simple HTML replacement --> <html> <![CDATA[<div class=\"videoContainer\"><iframe src=\"https://www.youtube.com/embed/videoseries?list={$ID}\" allowfullscreen></iframe></div>]]> </html> </provider> </import> <delete> <provider identifier= \"example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu-item/","text":"Menu Item Package Installation Plugin # Adds menu items to existing menus. Components # Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard . <menu> # The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml . <title> # The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values. <page> # The page that the link should point to, requires the internal identifier set by creating a page through the page.xml . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menuItem"},{"location":"package/pip/menu-item/#menu-item-package-installation-plugin","text":"Adds menu items to existing menus.","title":"Menu Item Package Installation Plugin"},{"location":"package/pip/menu-item/#components","text":"Each item is described as an <item> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.Dashboard .","title":"Components"},{"location":"package/pip/menu-item/#menu","text":"The target menu that the item should be added to, requires the internal identifier set by creating a menu through the menu.xml .","title":"&lt;menu&gt;"},{"location":"package/pip/menu-item/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple <title> elements to provide localized values.","title":"&lt;title&gt;"},{"location":"package/pip/menu-item/#page","text":"The page that the link should point to, requires the internal identifier set by creating a page through the page.xml .","title":"&lt;page&gt;"},{"location":"package/pip/menu-item/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.Dashboard\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Dashboard </title> <title language= \"en\" > Dashboard </title> <page> com.woltlab.wcf.Dashboard </page> </item> </import> <delete> <item identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/menu/","text":"Menu Package Installation Plugin # Deploy and manage menus that can be placed anywhere on the site. Components # Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu . <title> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements. <box> # The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"menu"},{"location":"package/pip/menu/#menu-package-installation-plugin","text":"Deploy and manage menus that can be placed anywhere on the site.","title":"Menu Package Installation Plugin"},{"location":"package/pip/menu/#components","text":"Each item is described as a <menu> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<MenuName> , e.g. com.woltlab.wcf.MainMenu .","title":"Components"},{"location":"package/pip/menu/#title","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <title> elements.","title":"&lt;title&gt;"},{"location":"package/pip/menu/#box","text":"The following elements of the box PIP are supported, please refer to the documentation to learn more about them: <position> <showHeader> <visibleEverywhere> <visibilityExceptions> cssClassName","title":"&lt;box&gt;"},{"location":"package/pip/menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd\" > <import> <menu identifier= \"com.woltlab.wcf.FooterLinks\" > <title language= \"de\" > Footer-Links </title> <title language= \"en\" > Footer Links </title> <box> <position> footer </position> <cssClassName> boxMenuLinkGroup </cssClassName> <showHeader> 0 </showHeader> <visibleEverywhere> 1 </visibleEverywhere> </box> </menu> </import> <delete> <menu identifier= \"com.woltlab.wcf.FooterLinks\" /> </delete> </data>","title":"Example"},{"location":"package/pip/object-type-definition/","text":"Object Type Definition Package Installation Plugin # Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system. Components # Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <interfacename> # Optional The name of the PHP interface objectTypes have to implement. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"objectTypeDefinition"},{"location":"package/pip/object-type-definition/#object-type-definition-package-installation-plugin","text":"Registers an object type definition. An object type definition is a blueprint for a certain behaviour that is particularized by objectTypes . As an example: Tags can be attached to different types of content (such as forum posts or gallery images). The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. Certain things, such as permission checking, need to be particularized for the specific type of content, though. Thus tags (or rather \u201ctaggable content\u201d) are registered as an object type definition. Posts are then registered as an object type, implementing the \u201ctaggable content\u201d behaviour. Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system.","title":"Object Type Definition Package Installation Plugin"},{"location":"package/pip/object-type-definition/#components","text":"Each item is described as a <definition> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type-definition/#interfacename","text":"Optional The name of the PHP interface objectTypes have to implement.","title":"&lt;interfacename&gt;"},{"location":"package/pip/object-type-definition/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectTypeDefinition.xsd\" > <import> <definition> <name> com.woltlab.wcf.example </name> <interfacename> wcf\\system\\example\\IExampleObjectType </interfacename> </definition> </import> </data>","title":"Example"},{"location":"package/pip/object-type/","text":"Object Type Package Installation Plugin # Registers an object type. Read about object types in the objectTypeDefinition PIP. Components # Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example . <definitionname> # The <name> of the objectTypeDefinition . <classname> # The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition. <*> # Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"objectType"},{"location":"package/pip/object-type/#object-type-package-installation-plugin","text":"Registers an object type. Read about object types in the objectTypeDefinition PIP.","title":"Object Type Package Installation Plugin"},{"location":"package/pip/object-type/#components","text":"Each item is described as a <type> element with the mandatory child <name> that should follow the naming pattern <packageIdentifier>.<definition> , e.g. com.woltlab.wcf.example .","title":"Components"},{"location":"package/pip/object-type/#definitionname","text":"The <name> of the objectTypeDefinition .","title":"&lt;definitionname&gt;"},{"location":"package/pip/object-type/#classname","text":"The name of the class providing the object types's behaviour, the class has to implement the <interfacename> interface of the object type definition.","title":"&lt;classname&gt;"},{"location":"package/pip/object-type/#_1","text":"Optional Additional fields may be defined for specific definitions of object types. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/object-type/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.example </name> <definitionname> com.woltlab.wcf.rebuildData </definitionname> <classname> wcf\\system\\worker\\ExampleRebuildWorker </classname> <nicevalue> 130 </nicevalue> </type> </import> </data>","title":"Example"},{"location":"package/pip/option/","text":"Option Package Installation Plugin # Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants. Category Components # Each category is described as an <category> element with the mandatory attribute name . <parent> # Optional The category\u2019s parent category. <showorder> # Optional Specifies the order of this option within the parent category. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator. Option Components # Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it. <categoryname> # The option\u2019s category. <optiontype> # The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes. <defaultvalue> # The value that is set after installation of a package. Valid values are defined by the optiontype . <validationpattern> # Optional Defines a regular expression that is used to validate the value of a free form option (such as text ). <showorder> # Optional Specifies the order of this option within the category. <selectoptions> # Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code. <enableoptions> # Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option. <hidden> # Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator. <supporti18n> # Optional Specifies whether this option supports localized input. <requirei18n> # Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language). <*> # Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation. Language Items # All relevant language items have to be put into the wcf.acp.option language item category. Categories # If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"option"},{"location":"package/pip/option/#option-package-installation-plugin","text":"Registers new options. Options allow the administrator to configure the behaviour of installed packages. The specified values are exposed as PHP constants.","title":"Option Package Installation Plugin"},{"location":"package/pip/option/#category-components","text":"Each category is described as an <category> element with the mandatory attribute name .","title":"Category Components"},{"location":"package/pip/option/#parent","text":"Optional The category\u2019s parent category.","title":"&lt;parent&gt;"},{"location":"package/pip/option/#showorder","text":"Optional Specifies the order of this option within the parent category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#option-components","text":"Each option is described as an <option> element with the mandatory attribute name . The name is transformed into a PHP constant name by uppercasing it.","title":"Option Components"},{"location":"package/pip/option/#categoryname","text":"The option\u2019s category.","title":"&lt;categoryname&gt;"},{"location":"package/pip/option/#optiontype","text":"The type of input to be used for this option. Valid types are defined by the wcf\\system\\option\\*OptionType classes.","title":"&lt;optiontype&gt;"},{"location":"package/pip/option/#defaultvalue","text":"The value that is set after installation of a package. Valid values are defined by the optiontype .","title":"&lt;defaultvalue&gt;"},{"location":"package/pip/option/#validationpattern","text":"Optional Defines a regular expression that is used to validate the value of a free form option (such as text ).","title":"&lt;validationpattern&gt;"},{"location":"package/pip/option/#showorder_1","text":"Optional Specifies the order of this option within the category.","title":"&lt;showorder&gt;"},{"location":"package/pip/option/#selectoptions","text":"Optional Defined only for select , multiSelect and radioButton types. Specifies a newline-separated list of selectable values. Each line consists of an internal handle, followed by a colon ( : , U+003A), followed by a language item. The language item is shown to the administrator, the internal handle is what is saved and exposed to the code.","title":"&lt;selectoptions&gt;"},{"location":"package/pip/option/#enableoptions","text":"Optional Defined only for boolean , select and radioButton types. Specifies a comma-separated list of options which should be visually enabled when this option is enabled. A leading exclamation mark ( ! , U+0021) will disable the specified option when this option is enabled. For select and radioButton types the list should be prefixed by the internal selectoptions handle followed by a colon ( : , U+003A). This setting is a visual helper for the administrator only. It does not have an effect on the server side processing of the option.","title":"&lt;enableoptions&gt;"},{"location":"package/pip/option/#hidden","text":"Optional If hidden is set to 1 the option will not be shown to the administrator. It still can be modified programmatically.","title":"&lt;hidden&gt;"},{"location":"package/pip/option/#options_1","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the option to be shown to the administrator.","title":"&lt;options&gt;"},{"location":"package/pip/option/#supporti18n","text":"Optional Specifies whether this option supports localized input.","title":"&lt;supporti18n&gt;"},{"location":"package/pip/option/#requirei18n","text":"Optional Specifies whether this option requires localized input (i.e. the administrator must specify a value for every installed language).","title":"&lt;requirei18n&gt;"},{"location":"package/pip/option/#_1","text":"Optional Additional fields may be defined by specific types of options. Refer to the documentation of these for further explanation.","title":"&lt;*&gt;"},{"location":"package/pip/option/#language-items","text":"All relevant language items have to be put into the wcf.acp.option language item category.","title":"Language Items"},{"location":"package/pip/option/#categories","text":"If you install a category named example.sub , you have to provide the language item wcf.acp.option.category.example.sub , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.option.category.example.sub.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/option/#options_2","text":"If you install an option named module_example , you have to provide the language item wcf.acp.option.module_example , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.option.module_example.description .","title":"Options"},{"location":"package/pip/option/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/option.xsd\" > <import> <categories> <category name= \"example\" /> <category name= \"example.sub\" > <parent> example </parent> <options> module_example </options> </category> </categories> <options> <option name= \"module_example\" > <categoryname> module.community </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 1 </defaultvalue> </option> <option name= \"example_integer\" > <categoryname> example.sub </categoryname> <optiontype> integer </optiontype> <defaultvalue> 10 </defaultvalue> <minvalue> 5 </minvalue> <maxvalue> 40 </maxvalue> </option> <option name= \"example_select\" > <categoryname> example.sub </categoryname> <optiontype> select </optiontype> <defaultvalue> DESC </defaultvalue> <selectoptions> ASC:wcf.global.sortOrder.ascending DESC:wcf.global.sortOrder.descending </selectoptions> </option> </options> </import> <delete> <option name= \"outdated_example\" /> </delete> </data>","title":"Example"},{"location":"package/pip/page/","text":"Page Package Installation Plugin # Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus. Components # Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList . <pageType> # system # The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element. html , text or tpl # Provide arbitrary content, requires the <content> element. <controller> # Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm . <handler> # Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id. <name> # The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements. <parent> # Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy. <hasFixedParent> # Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable. <permissions> # The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set. <options> # The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set. <excludeFromLandingPage> # Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever. <content> # The language attribute is required and should specify the ISO-639-1 language code. <title> # The title element is required and controls the page title shown to the end users. <content> # The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl . Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"page"},{"location":"package/pip/page/#page-package-installation-plugin","text":"Registers page controllers, making them available for selection and configuration, including but not limited to boxes and menus.","title":"Page Package Installation Plugin"},{"location":"package/pip/page/#components","text":"Each item is described as a <page> element with the mandatory attribute identifier that should follow the naming pattern <packageIdentifier>.<PageName> , e.g. com.woltlab.wcf.MembersList .","title":"Components"},{"location":"package/pip/page/#pagetype","text":"","title":"&lt;pageType&gt;"},{"location":"package/pip/page/#system","text":"The special system type is reserved for pages that pull their properties and content from a registered PHP class. Requires the <controller> element.","title":"system"},{"location":"package/pip/page/#html-text-or-tpl","text":"Provide arbitrary content, requires the <content> element.","title":"html, text or tpl"},{"location":"package/pip/page/#controller","text":"Fully qualified class name for the controller, must implement wcf\\page\\IPage or wcf\\form\\IForm .","title":"&lt;controller&gt;"},{"location":"package/pip/page/#handler","text":"Fully qualified class name that can be optionally set to provide additional methods, such as displaying a badge for unread content and verifying permissions per page object id.","title":"&lt;handler&gt;"},{"location":"package/pip/page/#name","text":"The language attribute is required and should specify the ISO-639-1 language code. The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple <name> elements.","title":"&lt;name&gt;"},{"location":"package/pip/page/#parent","text":"Sets the default parent page using its internal identifier, this setting controls the breadcrumbs and active menu item hierarchy.","title":"&lt;parent&gt;"},{"location":"package/pip/page/#hasfixedparent","text":"Pages can be assigned any other page as parent page by default, set to 1 to make the parent setting immutable.","title":"&lt;hasFixedParent&gt;"},{"location":"package/pip/page/#permissions","text":"The comma represents a logical or , the check is successful if at least one permission is set. Comma separated list of permission names that will be checked one after another until at least one permission is set.","title":"&lt;permissions&gt;"},{"location":"package/pip/page/#options","text":"The comma represents a logical or , the check is successful if at least one option is enabled. Comma separated list of options that will be checked one after another until at least one option is set.","title":"&lt;options&gt;"},{"location":"package/pip/page/#excludefromlandingpage","text":"Some pages should not be used as landing page, because they may not always be available and/or accessible to the user. For example, the account management page is available to logged-in users only and any guest attempting to visit that page would be presented with a permission denied message. Set this to 1 to prevent this page from becoming a landing page ever.","title":"&lt;excludeFromLandingPage&gt;"},{"location":"package/pip/page/#content","text":"The language attribute is required and should specify the ISO-639-1 language code.","title":"&lt;content&gt;"},{"location":"package/pip/page/#title","text":"The title element is required and controls the page title shown to the end users.","title":"&lt;title&gt;"},{"location":"package/pip/page/#content_1","text":"The content that should be used to populate the page, only used and required if the pageType equals text , html and tpl .","title":"&lt;content&gt;"},{"location":"package/pip/page/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.MembersList\" > <pageType> system </pageType> <controller> wcf\\page\\MembersListPage </controller> <name language= \"de\" > Mitglieder </name> <name language= \"en\" > Members </name> <permissions> user.profile.canViewMembersList </permissions> <options> module_members_list </options> <content language= \"en\" > <title> Members </title> </content> <content language= \"de\" > <title> Mitglieder </title> </content> </page> </import> <delete> <page identifier= \"com.woltlab.wcf.MembersList\" /> </delete> </data>","title":"Example"},{"location":"package/pip/pip/","text":"Package Installation Plugin Package Installation Plugin # Registers new package installation plugins. Components # Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation! Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"pip"},{"location":"package/pip/pip/#package-installation-plugin-package-installation-plugin","text":"Registers new package installation plugins.","title":"Package Installation Plugin Package Installation Plugin"},{"location":"package/pip/pip/#components","text":"Each package installation plugin is described as an <pip> element with a name attribute and a PHP classname as the text content. The package installation plugin\u2019s class file must be installed into the wcf application and must not include classes outside the \\wcf\\* hierarchy to allow for proper uninstallation!","title":"Components"},{"location":"package/pip/pip/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/packageInstallationPlugin.xsd\" > <import> <pip name= \"custom\" > wcf\\system\\package\\plugin\\CustomPackageInstallationPlugin </pip> </import> <delete> <pip name= \"outdated\" /> </delete> </data>","title":"Example"},{"location":"package/pip/script/","text":"Script Package Installation Plugin # Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution. Attributes # application # The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page . Expected value # The script -PIP expects a relative path to a .php file. Naming convention # The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 . Execution environment # The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"script"},{"location":"package/pip/script/#script-package-installation-plugin","text":"Execute arbitrary PHP code during installation, update and uninstallation of the package. You must install the PHP script through the file package installation plugin . The installation will attempt to delete the script after successful execution.","title":"Script Package Installation Plugin"},{"location":"package/pip/script/#attributes","text":"","title":"Attributes"},{"location":"package/pip/script/#application","text":"The application attribute must have the same value as the application attribute of the file package installation plugin instruction so that the correct file in the intended application directory is executed. For further information about the application attribute, refer to its documentation on the acpTemplate package installation plugin page .","title":"application"},{"location":"package/pip/script/#expected-value","text":"The script -PIP expects a relative path to a .php file.","title":"Expected value"},{"location":"package/pip/script/#naming-convention","text":"The PHP script is deployed by using the file package installation plugin . To prevent it from colliding with other install script (remember: You cannot overwrite files created by another plugin), we highly recommend to make use of these naming conventions: Installation: install_<package>_<version>.php (example: install_com.woltlab.wbb_5.0.0.php ) Update: update_<package>_<targetVersion>.php (example: update_com.woltlab.wbb_5.0.0_pl_1.php ) <targetVersion> equals the version number of the current package being installed. If you're updating from 1.0.0 to 1.0.1 , <targetVersion> should read 1.0.1 .","title":"Naming convention"},{"location":"package/pip/script/#execution-environment","text":"The script is included using include() within ScriptPackageInstallationPlugin::run() . This grants you access to the class members, including $this->installation . You can retrieve the package id of the current package through $this->installation->getPackageID() .","title":"Execution environment"},{"location":"package/pip/smiley/","text":"Smiley Package Installation Plugin # Installs new smileys. Components # Each smiley is described as an <smiley> element with the mandatory attribute name . <title> # Short human readable description of the smiley. <path(2x)?> # The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens. <aliases> # Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A). <showorder> # Optional Determines at which position of the smiley list the smiley is shown. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"smiley"},{"location":"package/pip/smiley/#smiley-package-installation-plugin","text":"Installs new smileys.","title":"Smiley Package Installation Plugin"},{"location":"package/pip/smiley/#components","text":"Each smiley is described as an <smiley> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/smiley/#title","text":"Short human readable description of the smiley.","title":"&lt;title&gt;"},{"location":"package/pip/smiley/#path2x","text":"The files must be installed using the file PIP. File path relative to the root of WoltLab Suite Core. path2x is optional and being used for High-DPI screens.","title":"&lt;path(2x)?&gt;"},{"location":"package/pip/smiley/#aliases","text":"Optional List of smiley aliases. Aliases must be separated by a line feed character ( \\n , U+000A).","title":"&lt;aliases&gt;"},{"location":"package/pip/smiley/#showorder","text":"Optional Determines at which position of the smiley list the smiley is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/smiley/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/smiley.xsd\" > <import> <smiley name= \":example:\" > <title> example </title> <path> images/smilies/example.png </path> <path2x> images/smilies/example@2x.png </path2x> <aliases> <![CDATA[:alias: :more_aliases:]]> </aliases> </smiley> </import> </data>","title":"Example"},{"location":"package/pip/sql/","text":"SQL Package Installation Plugin # Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard. Expected Value # The sql package installation plugin expects a relative path to a .sql file. Features # Logging # WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY Instance Number # It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 ! Table Type # WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used. Limitations # Logging # WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE . Triggers # WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script . Example # package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"sql"},{"location":"package/pip/sql/#sql-package-installation-plugin","text":"Execute SQL instructions using a MySQL-flavored syntax. This file is parsed by WoltLab Suite Core to allow reverting of certain changes, but not every syntax MySQL supports is recognized by the parser. To avoid any troubles, you should always use statements relying on the SQL standard.","title":"SQL Package Installation Plugin"},{"location":"package/pip/sql/#expected-value","text":"The sql package installation plugin expects a relative path to a .sql file.","title":"Expected Value"},{"location":"package/pip/sql/#features","text":"","title":"Features"},{"location":"package/pip/sql/#logging","text":"WoltLab Suite Core uses a SQL parser to extract queries and log certain actions. This allows WoltLab Suite Core to revert some of the changes you apply upon package uninstallation. The logged changes are: CREATE TABLE ALTER TABLE \u2026 ADD COLUMN ALTER TABLE \u2026 ADD \u2026 KEY","title":"Logging"},{"location":"package/pip/sql/#instance-number","text":"It is possible to use different instance numbers, e.g. two separate WoltLab Suite Core installations within one database. WoltLab Suite Core requires you to always use wcf1_<tableName> or <app>1_<tableName> (e.g. blog1_blog in WoltLab Suite Blog), the number ( 1 ) will be automatically replaced prior to execution. If you every use anything other but 1 , you will eventually break things, thus always use 1 !","title":"Instance Number"},{"location":"package/pip/sql/#table-type","text":"WoltLab Suite Core will determine the type of database tables on its own: If the table contains a FULLTEXT index, it uses MyISAM , otherwise InnoDB is used.","title":"Table Type"},{"location":"package/pip/sql/#limitations","text":"","title":"Limitations"},{"location":"package/pip/sql/#logging_1","text":"WoltLab Suite Core cannot revert changes to the database structure which would cause to the data to be either changed or new data to be incompatible with the original format. Additionally, WoltLab Suite Core does not track regular SQL queries such as DELETE or UPDATE .","title":"Logging"},{"location":"package/pip/sql/#triggers","text":"WoltLab Suite Core does not support trigger since MySQL does not support execution of triggers if the event was fired by a cascading foreign key action. If you really need triggers, you should consider adding them by custom SQL queries using a script .","title":"Triggers"},{"location":"package/pip/sql/#example","text":"package.xml : <instruction type= \"sql\" > install.sql </instruction> Example content: CREATE TABLE wcf1_foo_bar ( fooID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , packageID INT ( 10 ) NOT NULL , bar VARCHAR ( 255 ) NOT NULL DEFAULT '' , foobar VARCHAR ( 50 ) NOT NULL DEFAULT '' , UNIQUE KEY baz ( bar , foobar ) ); ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY ( packageID ) REFERENCES wcf1_package ( packageID ) ON DELETE CASCADE ;","title":"Example"},{"location":"package/pip/style/","text":"Style Package Installation Plugin # Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles. Example in package.xml # <instruction type= \"style\" > style.tgz </instruction>","title":"style"},{"location":"package/pip/style/#style-package-installation-plugin","text":"Install styles during package installation. The style package installation plugins expects a relative path to a .tar file, a .tar.gz file or a .tgz file. Please use the ACP's export mechanism to export styles.","title":"Style Package Installation Plugin"},{"location":"package/pip/style/#example-in-packagexml","text":"<instruction type= \"style\" > style.tgz </instruction>","title":"Example in package.xml"},{"location":"package/pip/template-listener/","text":"Template Listener Package Installation Plugin # Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables). Components # Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database. <templatename> # The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners. <eventname> # The event name is the name given when the event is fired to identify different events within the same template. <templatecode> # The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention. <environment> # The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ). <nice> # Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"templateListener"},{"location":"package/pip/template-listener/#template-listener-package-installation-plugin","text":"Registers template listeners. Template listeners supplement event listeners , which modify server side behaviour, by adding additional template code to display additional elements. The added template code behaves as if it was part of the original template (i.e. it has access to all local variables).","title":"Template Listener Package Installation Plugin"},{"location":"package/pip/template-listener/#components","text":"Each event listener is described as an <templatelistener> element with a name attribute. As the name attribute has only be introduced with WSC 3.0, it is not yet mandatory to allow backwards compatibility. If name is not given, the system automatically sets the name based on the id of the event listener in the database.","title":"Components"},{"location":"package/pip/template-listener/#templatename","text":"The template name is the name of the template in which the event is fired. It correspondes to the eventclassname field of event listeners.","title":"&lt;templatename&gt;"},{"location":"package/pip/template-listener/#eventname","text":"The event name is the name given when the event is fired to identify different events within the same template.","title":"&lt;eventname&gt;"},{"location":"package/pip/template-listener/#templatecode","text":"The given template code is literally copied into the target template during compile time. The original template is not modified. If multiple template listeners listen to a single event their output is concatenated using the line feed character ( \\n , U+000A) in the order defined by the niceValue . It is recommend that the only code is an {include} of a template to enable changes by the administrator. Names of templates included by a template listener start with two underscores by convention.","title":"&lt;templatecode&gt;"},{"location":"package/pip/template-listener/#environment","text":"The value of the environment element can either be admin or user and is user if no value is given. The value determines if the template listener will be executed in the frontend ( user ) or the backend ( admin ).","title":"&lt;environment&gt;"},{"location":"package/pip/template-listener/#nice","text":"Optional The nice value element can contain an integer value out of the interval [-128,127] with 0 being the default value if the element is omitted. The nice value determines the execution order of template listeners. Template listeners with smaller nice values are executed first. If the nice value of two template listeners is equal, the order is undefined. If you pass a value out of the mentioned interval, the value will be adjusted to the closest value in the interval.","title":"&lt;nice&gt;"},{"location":"package/pip/template-listener/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the template listener to be executed.","title":"&lt;options&gt;"},{"location":"package/pip/template-listener/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the template listener to be executed.","title":"&lt;permissions&gt;"},{"location":"package/pip/template-listener/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/templatelistener.xsd\" > <import> <templatelistener name= \"example\" > <environment> user </environment> <templatename> headIncludeJavaScript </templatename> <eventname> javascriptInclude </eventname> <templatecode> <![CDATA[{include file='__myCustomJavaScript'}]]> </templatecode> </templatelistener> </import> <delete> <templatelistener name= \"oldTemplateListenerName\" /> </delete> </data>","title":"Example"},{"location":"package/pip/template/","text":"Template Package Installation Plugin # Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"template"},{"location":"package/pip/template/#template-package-installation-plugin","text":"Add templates for frontend pages and forms by providing an archive containing the template files. You cannot overwrite templates provided by other packages. This package installation plugin behaves exactly like the acpTemplate package installation plugin except for installing frontend templates instead of backend/acp templates.","title":"Template Package Installation Plugin"},{"location":"package/pip/user-group-option/","text":"User Group Option Package Installation Plugin # Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <(admin|mod|user)defaultvalue> # Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS . <usersonly> # Makes the option unavailable for groups with the group type UserGroup::GUESTS . Language Items # All relevant language items have to be put into the wcf.acp.group language item category. Categories # If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"userGroupOption"},{"location":"package/pip/user-group-option/#user-group-option-package-installation-plugin","text":"Registers new user group options (\u201cpermissions\u201d). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Group Option Package Installation Plugin"},{"location":"package/pip/user-group-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-group-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-group-option/#adminmoduserdefaultvalue","text":"Defines the defaultvalue s for subsets of the groups: Type Description admin Groups where the admin.user.accessibleGroups user group option includes every group. mod Groups where the mod.general.canUseModeration is set to true . user Groups where the internal group type is neither UserGroup::EVERYONE nor UserGroup::GUESTS .","title":"&lt;(admin|mod|user)defaultvalue&gt;"},{"location":"package/pip/user-group-option/#usersonly","text":"Makes the option unavailable for groups with the group type UserGroup::GUESTS .","title":"&lt;usersonly&gt;"},{"location":"package/pip/user-group-option/#language-items","text":"All relevant language items have to be put into the wcf.acp.group language item category.","title":"Language Items"},{"location":"package/pip/user-group-option/#categories","text":"If you install a category named user.foo , you have to provide the language item wcf.acp.group.option.category.user.foo , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.acp.group.option.category.user.foo.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-group-option/#options","text":"If you install an option named user.foo.canBar , you have to provide the language item wcf.acp.group.option.user.foo.canBar , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.acp.group.option.user.foo.canBar.description .","title":"Options"},{"location":"package/pip/user-menu/","text":"User Menu Package Installation Plugin # Registers new user menu items. Components # Each item is described as an <usermenuitem> element with the mandatory attribute name . <parent> # Optional The item\u2019s parent item. <showorder> # Optional Specifies the order of this item within the parent item. <controller> # The fully qualified class name of the target controller. If not specified this item serves as a category. <link> # Additional components if <controller> is set, the full external link otherwise. <iconclassname> # Use an icon only for top-level items. Name of the Font Awesome icon class. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown. <classname> # The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"userMenu"},{"location":"package/pip/user-menu/#user-menu-package-installation-plugin","text":"Registers new user menu items.","title":"User Menu Package Installation Plugin"},{"location":"package/pip/user-menu/#components","text":"Each item is described as an <usermenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-menu/#parent","text":"Optional The item\u2019s parent item.","title":"&lt;parent&gt;"},{"location":"package/pip/user-menu/#showorder","text":"Optional Specifies the order of this item within the parent item.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-menu/#controller","text":"The fully qualified class name of the target controller. If not specified this item serves as a category.","title":"&lt;controller&gt;"},{"location":"package/pip/user-menu/#link","text":"Additional components if <controller> is set, the full external link otherwise.","title":"&lt;link&gt;"},{"location":"package/pip/user-menu/#iconclassname","text":"Use an icon only for top-level items. Name of the Font Awesome icon class.","title":"&lt;iconclassname&gt;"},{"location":"package/pip/user-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the menu item to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the menu item to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-menu/#classname","text":"The name of the class providing the user menu item\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\IUserMenuItemProvider interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-menu/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userMenu.xsd\" > <import> <usermenuitem name= \"wcf.user.menu.foo\" > <iconclassname> fa-home </iconclassname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.bar\" > <controller> wcf\\page\\FooBarListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBar </permissions> <classname> wcf\\system\\menu\\user\\FooBarMenuItemProvider </classname> </usermenuitem> <usermenuitem name= \"wcf.user.menu.foo.baz\" > <controller> wcf\\page\\FooBazListPage </controller> <parent> wcf.user.menu.foo </parent> <permissions> user.foo.canBaz </permissions> <options> module_foo_bar </options> </usermenuitem> </import> </data>","title":"Example"},{"location":"package/pip/user-notification-event/","text":"User Notification Event Package Installation Plugin # Registers new user notification events. Components # Each package installation plugin is described as an <event> element with the mandatory child <name> . <objectType> # The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition. <classname> # The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface. <preset> # Defines whether this event is enabled by default. <presetmailnotificationtype> # Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available. Example # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"userNotificationEvent"},{"location":"package/pip/user-notification-event/#user-notification-event-package-installation-plugin","text":"Registers new user notification events.","title":"User Notification Event Package Installation Plugin"},{"location":"package/pip/user-notification-event/#components","text":"Each package installation plugin is described as an <event> element with the mandatory child <name> .","title":"Components"},{"location":"package/pip/user-notification-event/#objecttype","text":"The (name, objectType) pair must be unique. The given object type must implement the com.woltlab.wcf.notification.objectType definition.","title":"&lt;objectType&gt;"},{"location":"package/pip/user-notification-event/#classname","text":"The name of the class providing the event's behaviour, the class has to implement the wcf\\system\\user\\notification\\event\\IUserNotificationEvent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-notification-event/#preset","text":"Defines whether this event is enabled by default.","title":"&lt;preset&gt;"},{"location":"package/pip/user-notification-event/#presetmailnotificationtype","text":"Avoid using this option, as sending unsolicited mail can be seen as spamming. One of instant or daily . Defines whether this type of email notifications is enabled by default.","title":"&lt;presetmailnotificationtype&gt;"},{"location":"package/pip/user-notification-event/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the notification type to be available.","title":"&lt;options&gt;"},{"location":"package/pip/user-notification-event/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the notification type to be available.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-notification-event/#example","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> like </name> <objecttype> com.woltlab.example.comment.like.notification </objecttype> <classname> wcf\\system\\user\\notification\\event\\ExampleCommentLikeUserNotificationEvent </classname> <preset> 1 </preset> <options> module_like </options> </event> </import> </data>","title":"Example"},{"location":"package/pip/user-option/","text":"User Option Package Installation Plugin # Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP. Category Components # The category definition works exactly like the option PIP. Option Components # The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined: <required> # Requires that a value is provided. <askduringregistration> # If set to 1 the field is shown during user registration in the frontend. <editable> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4 <visible> # Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8 <searchable> # If set to 1 the field is searchable. <outputclass> # PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface. Language Items # All relevant language items have to be put into the wcf.user.option language item category. Categories # If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level. Options # If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"userOption"},{"location":"package/pip/user-option/#user-option-package-installation-plugin","text":"Registers new user options (profile fields / user settings). The behaviour of this package installation plugin closely follows the option PIP.","title":"User Option Package Installation Plugin"},{"location":"package/pip/user-option/#category-components","text":"The category definition works exactly like the option PIP.","title":"Category Components"},{"location":"package/pip/user-option/#option-components","text":"The fields hidden , supporti18n and requirei18n do not apply. The following extra fields are defined:","title":"Option Components"},{"location":"package/pip/user-option/#required","text":"Requires that a value is provided.","title":"&lt;required&gt;"},{"location":"package/pip/user-option/#askduringregistration","text":"If set to 1 the field is shown during user registration in the frontend.","title":"&lt;askduringregistration&gt;"},{"location":"package/pip/user-option/#editable","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value EDITABILITY_OWNER 1 EDITABILITY_ADMINISTRATOR 2 EDITABILITY_OWNER_DURING_REGISTRATION 4","title":"&lt;editable&gt;"},{"location":"package/pip/user-option/#visible","text":"Bitfield with the following options (constants in wcf\\data\\user\\option\\UserOption ) Name Value VISIBILITY_OWNER 1 VISIBILITY_ADMINISTRATOR 2 VISIBILITY_REGISTERED 4 VISIBILITY_GUEST 8","title":"&lt;visible&gt;"},{"location":"package/pip/user-option/#searchable","text":"If set to 1 the field is searchable.","title":"&lt;searchable&gt;"},{"location":"package/pip/user-option/#outputclass","text":"PHP class responsible for output formatting of this field. the class has to implement the wcf\\system\\option\\user\\IUserOptionOutput interface.","title":"&lt;outputclass&gt;"},{"location":"package/pip/user-option/#language-items","text":"All relevant language items have to be put into the wcf.user.option language item category.","title":"Language Items"},{"location":"package/pip/user-option/#categories","text":"If you install a category named example , you have to provide the language item wcf.user.option.category.example , which is used when displaying the options. If you want to provide an optional description of the category, you have to provide the language item wcf.user.option.category.example.description . Descriptions are only relevant for categories whose parent has a parent itself, i.e. categories on the third level.","title":"Categories"},{"location":"package/pip/user-option/#options","text":"If you install an option named exampleOption , you have to provide the language item wcf.user.option.exampleOption , which is used as a label for setting the option value. If you want to provide an optional description of the option, you have to provide the language item wcf.user.option.exampleOption.description .","title":"Options"},{"location":"package/pip/user-profile-menu/","text":"User Profile Menu Package Installation Plugin # Registers new user profile tabs. Components # Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name . <classname> # The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface. <showorder> # Optional Determines at which position of the tab list the tab is shown. <options> # Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown. <permissions> # Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown. Example # <?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"userProfileMenu"},{"location":"package/pip/user-profile-menu/#user-profile-menu-package-installation-plugin","text":"Registers new user profile tabs.","title":"User Profile Menu Package Installation Plugin"},{"location":"package/pip/user-profile-menu/#components","text":"Each tab is described as an <userprofilemenuitem> element with the mandatory attribute name .","title":"Components"},{"location":"package/pip/user-profile-menu/#classname","text":"The name of the class providing the tab\u2019s behaviour, the class has to implement the wcf\\system\\menu\\user\\profile\\content\\IUserProfileMenuContent interface.","title":"&lt;classname&gt;"},{"location":"package/pip/user-profile-menu/#showorder","text":"Optional Determines at which position of the tab list the tab is shown.","title":"&lt;showorder&gt;"},{"location":"package/pip/user-profile-menu/#options","text":"Optional The options element can contain a comma-separated list of options of which at least one needs to be enabled for the tab to be shown.","title":"&lt;options&gt;"},{"location":"package/pip/user-profile-menu/#permissions","text":"Optional The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the tab to be shown.","title":"&lt;permissions&gt;"},{"location":"package/pip/user-profile-menu/#example","text":"<?xml version=\"1.0\" encoding=\"utf-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userProfileMenu.xsd\" > <import> <userprofilemenuitem name= \"example\" > <classname> wcf\\system\\menu\\user\\profile\\content\\ExampleProfileMenuContent </classname> <showorder> 3 </showorder> <options> module_example </options> </userprofilemenuitem> </import> </data>","title":"Example"},{"location":"php/apps/","text":"Apps for WoltLab Suite # Introduction # Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No. Differences to Plugins # Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link. Unique Namespace # Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" /> Unique Database Table Prefix # All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number. Separate Domain and Path Configuration # Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself. Creating an App # This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa. package.xml # The package.xml supports two additional elements in the <packageinformation> block that are unique to applications. <isapplication>1</isapplication> # This element is responsible to flag a package as an app. <applicationdirectory>example</applicationdirectory> # Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead. Minimum Required Files # An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Apps"},{"location":"php/apps/#apps-for-woltlab-suite","text":"","title":"Apps for WoltLab Suite"},{"location":"php/apps/#introduction","text":"Apps are among the most powerful components in WoltLab Suite. Unlike plugins that extend an existing functionality and pages, apps have their own frontend with a dedicated namespace, database table prefixes and template locations. However, apps are meant to be a logical (and to some extent physical) separation from other parts of the framework, including other installed apps. They offer an additional layer of isolation and enable you to re-use class and template names that are already in use by the Core itself. If you've come here, thinking about the question if your next package should be an app instead of a regular plugin, the result is almost always: No.","title":"Introduction"},{"location":"php/apps/#differences-to-plugins","text":"Apps do offer a couple of unique features that are not available to plugins and there are valid reasons to use one instead of a plugin, but they also increase both the code and system complexity. There is a performance penalty for each installed app, regardless if it is actively used in a request or not, simplying being there forces the Core to include it in many places, for example, class resolution or even simple tasks such as constructing a link.","title":"Differences to Plugins"},{"location":"php/apps/#unique-namespace","text":"Each app has its own unique namespace that is entirely separated from the Core and any other installed apps. The namespace is derived from the last part of the package identifier, for example, com.example.foo will yield the namespace foo . The namespace is always relative to the installation directory of the app, it doesn't matter if the app is installed on example.com/foo/ or in example.com/bar/ , the namespace will always resolve to the right directory. This app namespace is also used for ACP templates, frontend templates and files: <!-- somewhere in the package.xml --> <instructions type= \"file\" application= \"foo\" />","title":"Unique Namespace"},{"location":"php/apps/#unique-database-table-prefix","text":"All database tables make use of a generic prefix that is derived from one of the installed apps, including wcf which resolves to the Core itself. Following the aforementioned example, the new prefix fooN_ will be automatically registered and recognized in any generated statement. Any DatabaseObject that uses the app's namespace is automatically assumed to use the app's database prefix. For instance, foo\\data\\bar\\Bar is implicitly mapped to the database table fooN_bar . The app prefix is recognized in SQL-PIPs and statements that reference one of its database tables are automatically rewritten to use the Core's instance number.","title":"Unique Database Table Prefix"},{"location":"php/apps/#separate-domain-and-path-configuration","text":"Any controller that is provided by a plugin is served from the configured domain and path of the corresponding app, such as plugins for the Core are always served from the Core's directory. Apps are different and use their own domain and/or path to present their content, additionally, this allows the app to re-use a controller name that is already provided by the Core or any other app itself.","title":"Separate Domain and Path Configuration"},{"location":"php/apps/#creating-an-app","text":"This is a non-reversible operation! Once a package has been installed, its type cannot be changed without uninstalling and reinstalling the entire package, an app will always be an app and vice versa.","title":"Creating an App"},{"location":"php/apps/#packagexml","text":"The package.xml supports two additional elements in the <packageinformation> block that are unique to applications.","title":"package.xml"},{"location":"php/apps/#isapplication1isapplication","text":"This element is responsible to flag a package as an app.","title":"&lt;isapplication&gt;1&lt;/isapplication&gt;"},{"location":"php/apps/#applicationdirectoryexampleapplicationdirectory","text":"Sets the suggested name of the application directory when installing it, the path result in <path-to-the-core>/example/ . If you leave this element out, the app identifier ( com.example.foo -> foo ) will be used instead.","title":"&lt;applicationdirectory&gt;example&lt;/applicationdirectory&gt;"},{"location":"php/apps/#minimum-required-files","text":"An example project with the source code can be found on GitHub , it includes everything that is required for a basic app.","title":"Minimum Required Files"},{"location":"php/code-style-documentation/","text":"Documentation # The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. Database Objects # Database Table Columns as Properties # As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type Database Object Editors # Class Tags # Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself. Runtime Caches # Class Tags # Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Documentation"},{"location":"php/code-style-documentation/#documentation","text":"The following documentation conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so.","title":"Documentation"},{"location":"php/code-style-documentation/#database-objects","text":"","title":"Database Objects"},{"location":"php/code-style-documentation/#database-table-columns-as-properties","text":"As the database table columns are not explicit properties of the classes extending wcf\\data\\DatabaseObject but rather stored in DatabaseObject::$data and accessible via DatabaseObject::__get($name) , the IDE we use, PhpStorm, is neither able to autocomplete such property access nor to interfere the type of the property. To solve this problem, @property-read tags must be added to the class documentation which registers the database table columns as public read-only properties: * @property-read propertyType $propertyName property description The properties have to be in the same order as the order in the database table. The following table provides templates for common description texts so that similar database table columns have similar description texts. property description template and example unique object id unique id of the {object name} example: unique id of the acl option id of the delivering package id of the package which delivers the {object name} example: id of the package which delivers the acl option show order for nested structure position of the {object name} in relation to its siblings example: position of the ACP menu item in relation to its siblings show order within different object position of the {object name} in relation to the other {object name}s in the {parent object name} example: position of the label in relation to the other labels in the label group required permissions comma separated list of user group permissions of which the active user needs to have at least one to see (access, \u2026) the {object name} example: comma separated list of user group permissions of which the active user needs to have at least one to see the ACP menu item required options comma separated list of options of which at least one needs to be enabled for the {object name} to be shown (accessible, \u2026) example: comma separated list of options of which at least one needs to be enabled for the ACP menu item to be shown id of the user who has created the object id of the user who created (wrote, \u2026) the {object name} (or `null` if the user does not exist anymore (or if the {object name} has been created by a guest)) example: id of the user who wrote the comment or `null` if the user does not exist anymore or if the comment has been written by a guest name of the user who has created the object name of the user (or guest) who created (wrote, \u2026) the {object name} example: name of the user or guest who wrote the comment additional data array with additional data of the {object name} example: array with additional data of the user activity event time-related columns timestamp at which the {object name} has been created (written, \u2026) example: timestamp at which the comment has been written boolean options is `1` (or `0`) if the {object name} \u2026 (and thus \u2026), otherwise `0` (or `1`) example: is `1` if the ad is disabled and thus not shown, otherwise `0` $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the {object name} example: cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article $comments number of comments on the {object name} example: number of comments on the article $views number of times the {object name} has been viewed example: number of times the article has been viewed text field with potential language item name as value {text type} of the {object name} or name of language item which contains the {text type} example: description of the cronjob or name of language item which contains the description $objectTypeID id of the `{object type definition name}` object type example: id of the `com.woltlab.wcf.modifiableContent` object type","title":"Database Table Columns as Properties"},{"location":"php/code-style-documentation/#database-object-editors","text":"","title":"Database Object Editors"},{"location":"php/code-style-documentation/#class-tags","text":"Any database object editor class comment must have to following tags to properly support autocompletion by IDEs: /** * \u2026 * @method static {DBO class name} create(array $parameters = []) * @method {DBO class name} getDecoratedObject() * @mixin {DBO class name} */ The only exception to this rule is if the class overwrites the create() method which itself has to be properly documentation then. The first and second line makes sure that when calling the create() or getDecoratedObject() method, the return value is correctly recognized and not just a general DatabaseObject instance. The third line tells the IDE (if @mixin is supported) that the database object editor decorates the database object and therefore offers autocompletion for properties and methods from the database object class itself.","title":"Class Tags"},{"location":"php/code-style-documentation/#runtime-caches","text":"","title":"Runtime Caches"},{"location":"php/code-style-documentation/#class-tags_1","text":"Any class implementing the IRuntimeCache interface must have the following class tags: /** * \u2026 * @method {DBO class name}[] getCachedObjects() * @method {DBO class name} getObject($objectID) * @method {DBO class name}[] getObjects(array $objectIDs) */ These tags ensure that when calling any of the mentioned methods, the return value refers to the concrete database object and not just generically to DatabaseObject .","title":"Class Tags"},{"location":"php/code-style/","text":"Code Style # The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page . General Code Style # Naming conventions # The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING Arrays # For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ]; Ternary Operator # The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; Whitespaces # You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1) Classes # Referencing Class Names # If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ; Static Getters (of DatabaseObject Classes) # Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} . Long method calls # In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Code Style"},{"location":"php/code-style/#code-style","text":"The following code style conventions are used by us for our own packages. While you do not have to follow every rule, you are encouraged to do so. For information about how to document your code, please refer to the documentation page .","title":"Code Style"},{"location":"php/code-style/#general-code-style","text":"","title":"General Code Style"},{"location":"php/code-style/#naming-conventions","text":"The relevant naming conventions are: Upper camel case : The first letters of all compound words are written in upper case. Lower camel case : The first letters of compound words are written in upper case, except for the first letter which is written in lower case. Screaming snake case : All letters are written in upper case and compound words are separated by underscores. Type Convention Example Variable lower camel case $variableName Class upper camel case class UserGroupEditor Properties lower camel case public $propertyName Method lower camel case public function getObjectByName() Constant screaming snake case MODULE_USER_THING","title":"Naming conventions"},{"location":"php/code-style/#arrays","text":"For arrays, use the short array syntax introduced with PHP 5.4. The following example illustrates the different cases that can occur when working with arrays and how to format them: <? php $empty = []; $oneElement = [ 1 ]; $multipleElements = [ 1 , 2 , 3 ]; $oneElementWithKey = [ 'firstElement' => 1 ]; $multipleElementsWithKey = [ 'firstElement' => 1 , 'secondElement' => 2 , 'thirdElement' => 3 ];","title":"Arrays"},{"location":"php/code-style/#ternary-operator","text":"The ternary operator can be used for short conditioned assignments: <? php $name = isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ; The condition and the values should be short so that the code does not result in a very long line which thus decreases the readability compared to an if-else statement. Parentheses may only be used around the condition and not around the whole statement: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ); Parentheses around the conditions may not be used to wrap simple function calls: <? php // do not do it like this $name = ( isset ( $tagArgs [ 'name' ])) ? $tagArgs [ 'name' ] : 'default' ; but have to be used for comparisons or other binary operators: <? php $value = ( $otherValue > $upperLimit ) ? $additionalValue : $otherValue ; If you need to use more than one binary operator, use an if-else statement. The same rules apply to assigning array values: <? php $values = [ 'first' => $firstValue , 'second' => $secondToggle ? $secondValueA : $secondValueB , 'third' => ( $thirdToogle > 13 ) ? $thirdToogleA : $thirdToogleB ]; or return values: <? php return isset ( $tagArgs [ 'name' ]) ? $tagArgs [ 'name' ] : 'default' ;","title":"Ternary Operator"},{"location":"php/code-style/#whitespaces","text":"You have to put a whitespace in front of the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 opening bracket of a block public function test() { You have to put a whitespace behind the following things: equal sign in assignments: $x = 1; comparison operators: $x == 1 comma in a function/method parameter list if the comma is not followed by a line break: public function test($a, $b) { if , for , foreach , while : if ($x == 1)","title":"Whitespaces"},{"location":"php/code-style/#classes","text":"","title":"Classes"},{"location":"php/code-style/#referencing-class-names","text":"If you have to reference a class name inside a php file, you have to use the class keyword. <? php // not like this $className = 'wcf\\data\\example\\Example' ; // like this use wcf\\data\\example\\Example ; $className = Example :: class ;","title":"Referencing Class Names"},{"location":"php/code-style/#static-getters-of-databaseobject-classes","text":"Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like wcf\\data\\box\\Box::getBoxByIdentifier() : <? php namespace wcf\\data\\box ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; class Box extends DatabaseObject { /** * Returns the box with the given identifier. * * @param string $identifier * @return Box|null */ public static function getBoxByIdentifier ( $identifier ) { $sql = \"SELECT * FROM wcf\" . WCF_N . \"_box WHERE identifier = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $identifier ]); return $statement -> fetchObject ( self :: class ); } } Such methods should always either return the desired object or null if the object does not exist. wcf\\system\\database\\statement\\PreparedStatement::fetchObject() already takes care of this distinction so that its return value can simply be returned by such methods. The name of such getters should generally follow the convention get{object type}By{column or other description} .","title":"Static Getters (of DatabaseObject Classes)"},{"location":"php/code-style/#long-method-calls","text":"In some instances, methods with many argument have to be called which can result in lines of code like this one: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); which is hardly readable. Therefore, the line must be split into multiple lines with each argument in a separate line: <? php \\wcf\\system\\search\\SearchIndexManager :: getInstance () -> set ( 'com.woltlab.wcf.article' , $articleContent -> articleContentID , $articleContent -> content , $articleContent -> title , $articles [ $articleContent -> articleID ] -> time , $articles [ $articleContent -> articleID ] -> userID , $articles [ $articleContent -> articleID ] -> username , $articleContent -> languageID , $articleContent -> teaser ); In general, this rule applies to the following methods: wcf\\system\\edit\\EditHistoryManager::add() wcf\\system\\message\\quote\\MessageQuoteManager::addQuote() wcf\\system\\message\\quote\\MessageQuoteManager::getQuoteID() wcf\\system\\search\\SearchIndexManager::set() wcf\\system\\user\\object\\watch\\UserObjectWatchHandler::updateObject() wcf\\system\\user\\notification\\UserNotificationHandler::fireEvent()","title":"Long method calls"},{"location":"php/database-access/","text":"Database Access # Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works. The PreparedStatement Object # The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result } Query Parameters # The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result } Fetching a Single Result # Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor. Fetch by Column # There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn (); Fetching All Results # If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value. Building Complex Conditions # Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty! INSERT or UPDATE in Bulk # Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"Database Access"},{"location":"php/database-access/#database-access","text":"Database Objects provide a convenient and object-oriented approach to work with the database, but there can be use-cases that require raw access including writing methods for model classes. This section assumes that you have either used prepared statements before or at least understand how it works.","title":"Database Access"},{"location":"php/database-access/#the-preparedstatement-object","text":"The database access is designed around PreparedStatement , built on top of PHP's PDOStatement so that you call all of PDOStatement 's methods, and each query requires you to obtain a statement object. <? php $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( \"SELECT * FROM wcf\" . WCF_N . \"_example\" ); $statement -> execute (); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"The PreparedStatement Object"},{"location":"php/database-access/#query-parameters","text":"The example below illustrates the usage of parameters where each value is replaced with the generic ? -placeholder. Values are provided by calling $statement->execute() with a continuous, one-dimensional array that exactly match the number of question marks. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ? OR bar IN (?, ?, ?, ?, ?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute ([ $exampleID , $list , $of , $values , $for , $bar ]); while ( $row = $statement -> fetchArray ()) { // handle result }","title":"Query Parameters"},{"location":"php/database-access/#fetching-a-single-result","text":"Do not attempt to use fetchSingleRow() or fetchSingleColumn() if the result contains more than one row. You can opt-in to retrieve only a single row from database and make use of shortcut methods to reduce the code that you have to write. <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $row = $statement -> fetchSingleRow (); There are two distinct differences when comparing with the example on query parameters above: The method prepareStatement() receives a secondary parameter that will be appended to the query as LIMIT 1 . Data is read using fetchSingleRow() instead of fetchArray() or similar methods, that will read one result and close the cursor.","title":"Fetching a Single Result"},{"location":"php/database-access/#fetch-by-column","text":"There is no way to return another column from the same row if you use fetchColumn() to retrieve data. Fetching an array is only useful if there is going to be more than one column per result row, otherwise accessing the column directly is much more convenient and increases the code readability. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); while ( $bar = $statement -> fetchColumn ()) { // handle result } $bar = $statement -> fetchSingleColumn (); Similar to fetching a single row, you can also issue a query that will select a single row, but reads only one column from the result row. <? php $sql = \"SELECT bar FROM wcf\" . WCF_N . \"_example WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql , 1 ); $statement -> execute ([ $exampleID ]); $bar = $statement -> fetchSingleColumn ();","title":"Fetch by Column"},{"location":"php/database-access/#fetching-all-results","text":"If you want to fetch all results of a query but only store them in an array without directly processing them, in most cases, you can rely on built-in methods. To fetch all rows of query, you can use PDOStatement::fetchAll() with \\PDO::FETCH_ASSOC as the first parameter: <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $rows = $statement -> fetchAll ( \\PDO :: FETCH_ASSOC ); As a result, you get an array containing associative arrays with the rows of the wcf{WCF_N}_example database table as content. If you only want to fetch a list of the values of a certain column, you can use \\PDO::FETCH_COLUMN as the first parameter: <? php $sql = \"SELECT exampleID FROM wcf\" . WCF_N . \"_example\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $exampleIDs = $statement -> fetchAll ( \\PDO :: FETCH_COLUMN ); As a result, you get an array with all exampleID values. The PreparedStatement class adds an additional methods that covers another common use case in our code: Fetching two columns and using the first column's value as the array key and the second column's value as the array value. This case is covered by PreparedStatement::fetchMap() : <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' ); $map is a one-dimensional array where each exampleID value maps to the corresponding userID value. If there are multiple entries for a certain exampleID value with different userID values, the existing entry in the array will be overwritten and contain the last read value from the database table. Therefore, this method should generally only be used for unique combinations. If you do not have a combination of columns with unique pairs of values, but you want to get a list of userID values with the same exampleID , you can set the third parameter of fetchMap() to false and get a list: <? php $sql = \"SELECT exampleID, userID FROM wcf\" . WCF_N . \"_example_mapping\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); $map = $statement -> fetchMap ( 'exampleID' , 'userID' , false ); Now, as a result, you get a two-dimensional array with the array keys being the exampleID values and the array values being arrays with all userID values from rows with the respective exampleID value.","title":"Fetching All Results"},{"location":"php/database-access/#building-complex-conditions","text":"Building conditional conditions can turn out to be a real mess and it gets even worse with SQL's IN (\u2026) which requires as many placeholders as there will be values. The solutions is PreparedStatementConditionBuilder , a simple but useful helper class with a bulky name, it is also the class used when accessing DatabaseObjecList::getConditionBuilder() . <? php $conditions = new \\wcf\\system\\database\\util\\PreparedStatementConditionBuilder (); $conditions -> add ( \"exampleID = ?\" , [ $exampleID ]); if ( ! empty ( $valuesForBar )) { $conditions -> add ( \"(bar IN (?) OR baz = ?)\" , [ $valuesForBar , $baz ]); } The IN (?) in the example above is automatically expanded to match the number of items contained in $valuesForBar . Be aware that the method will generate an invalid query if $valuesForBar is empty!","title":"Building Complex Conditions"},{"location":"php/database-access/#insert-or-update-in-bulk","text":"Prepared statements not only protect against SQL injection by separating the logical query and the actual data, but also provides the ability to reuse the same query with different values. This leads to a performance improvement as the code does not have to transmit the query with for every data set and only has to parse and analyze the query once. <? php $data = [ 'abc' , 'def' , 'ghi' ]; $sql = \"INSERT INTO wcf\" . WCF_N . \"_example (bar) VALUES (?)\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $bar ) { $statement -> execute ([ $bar ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction (); It is generally advised to wrap bulk operations in a transaction as it allows the database to optimize the process, including fewer I/O operations. <? php $data = [ 1 => 'abc' , 3 => 'def' , 4 => 'ghi' ]; $sql = \"UPDATE wcf\" . WCF_N . \"_example SET bar = ? WHERE exampleID = ?\" ; $statement = \\wcf\\system\\WCF :: getDB () -> prepareStatement ( $sql ); \\wcf\\system\\WCF :: getDB () -> beginTransaction (); foreach ( $data as $exampleID => $bar ) { $statement -> execute ([ $bar , $exampleID ]); } \\wcf\\system\\WCF :: getDB () -> commitTransaction ();","title":"INSERT or UPDATE in Bulk"},{"location":"php/database-objects/","text":"Database Objects # WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text. DatabaseObject # The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually. DatabaseObjectDecorator # If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() . DatabaseObjectEditor # This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model. Create a new row # Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ; Updating an existing row # The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ; Deleting a row # Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete (); DatabaseObjectList # Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; } AbstractDatabaseObjectAction # Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; } Executing an Action # The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction Custom Method with AJAX Support # This section is about adding the method baz() to ExampleAction and calling it via AJAX. AJAX Validation # Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever! create, update and delete # The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } } Allow Invokation by Guests # Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods. ACP Access Only # Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"Database Objects"},{"location":"php/database-objects/#database-objects","text":"WoltLab Suite uses a unified interface to work with database rows using an object based approach instead of using native arrays holding arbitrary data. Each database table is mapped to a model class that is designed to hold a single record from that table and expose methods to work with the stored data, for example providing assistance when working with normalized datasets. Developers are required to provide the proper DatabaseObject implementations themselves, they're not automatically generated, all though the actual code that needs to be written is rather small. The following examples assume the fictional database table wcf1_example , exampleID as the auto-incrementing primary key and the column bar to store some text.","title":"Database Objects"},{"location":"php/database-objects/#databaseobject","text":"The basic model derives from wcf\\data\\DatabaseObject and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObject ; class Example extends DatabaseObject {} The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by DatabaseObject using the namespace and class name of the derived class. The example above uses the namespace wcf\\\u2026 which is used as table prefix and the class name Example is converted into exampleID , resulting in the database table name wcfN_example with the primary key exampleID . You can prevent this automatic guessing by setting the class properties $databaseTableName and $databaseTableIndexName manually.","title":"DatabaseObject"},{"location":"php/database-objects/#databaseobjectdecorator","text":"If you already have a DatabaseObject class and would like to extend it with additional data or methods, for example by providing a class ViewableExample which features view-related changes without polluting the original object, you can use DatabaseObjectDecorator which a default implementation of a decorator for database objects. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectDecorator ; class ViewableExample extends DatabaseObjectDecorator { protected static $baseClass = Example :: class ; public function getOutput () { $output = '' ; // [determine output] return $output ; } } It is mandatory to set the static $baseClass property to the name of the decorated class. Like for any decorator, you can directly access the decorated object's properties and methods for a decorated object by accessing the property or calling the method on the decorated object. You can access the decorated objects directly via DatabaseObjectDecorator::getDecoratedObject() .","title":"DatabaseObjectDecorator"},{"location":"php/database-objects/#databaseobjecteditor","text":"This is the low-level interface to manipulate data rows, it is recommended to use AbstractDatabaseObjectAction . Adding, editing and deleting models is done using the DatabaseObjectEditor class that decorates a DatabaseObject and uses its data to perform the actions. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectEditor ; class ExampleEditor extends DatabaseObjectEditor { protected static $baseClass = Example :: class ; } The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model.","title":"DatabaseObjectEditor"},{"location":"php/database-objects/#create-a-new-row","text":"Inserting a new row into the database table is provided through DatabaseObjectEditor::create() which yields a DatabaseObject instance after creation. <? php $example = \\wcf\\data\\example\\ExampleEditor :: create ([ 'bar' => 'Hello World!' ]); // output: Hello World! echo $example -> bar ;","title":"Create a new row"},{"location":"php/database-objects/#updating-an-existing-row","text":"The internal state of the decorated DatabaseObject is not altered at any point, the values will still be the same after editing or deleting the represented row. If you need an object with the latest data, you'll have to discard the current object and refetch the data from database. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> update ([ 'bar' => 'baz' ]); // output: Hello World! echo $example -> bar ; // re-creating the object will query the database again and retrieve the updated value $example = new \\wcf\\data\\example\\Example ( $example -> id ); // output: baz echo $example -> bar ;","title":"Updating an existing row"},{"location":"php/database-objects/#deleting-a-row","text":"Similar to the update process, the decorated DatabaseObject is not altered and will then point to an inexistent row. <? php $example = new \\wcf\\data\\example\\Example ( $id ); $exampleEditor = new \\wcf\\data\\example\\ExampleEditor ( $example ); $exampleEditor -> delete ();","title":"Deleting a row"},{"location":"php/database-objects/#databaseobjectlist","text":"Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the DatabaseObjectList object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models. <? php namespace wcf\\data\\example ; use wcf\\data\\DatabaseObjectList ; class ExampleList extends DatabaseObjectList { public $className = Example :: class ; } The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects. <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); // add constraints using the condition builder $exampleList -> getConditionBuilder () -> add ( 'bar IN (?)' , [[ 'Hello World!' , 'bar' , 'baz' ]]); // actually read the rows $exampleList -> readObjects (); foreach ( $exampleList as $example ) { echo $example -> bar ; } // retrieve the models directly instead of iterating over them $examples = $exampleList -> getObjects (); // just retrieve the number of rows $exampleCount = $exampleList -> countObjects (); DatabaseObjectList implements both SeekableIterator and Countable . Additionally, DatabaseObjectList objects has the following three public properties that are useful when fetching data with lists: $sqlLimit determines how many rows are fetched. If its value is 0 (which is the default value), all results are fetched. So be careful when dealing with large tables and you only want a limited number of rows: Set $sqlLimit to a value larger than zero! $sqlOffset : Paginated pages like a thread list use this feature a lot, it allows you to skip a given number of results. Imagine you want to display 20 threads per page but there are a total of 60 threads available. In this case you would specify $sqlLimit = 20 and $sqlOffset = 20 which will skip the first 20 threads, effectively displaying thread 21 to 40. $sqlOrderBy determines by which column(s) the rows are sorted in which order. Using our example in $sqlOffset you might want to display the 20 most recent threads on page 1, thus you should specify the order field and its direction, e.g. $sqlOrderBy = 'thread.lastPostTime DESC' which returns the most recent thread first. For more advanced usage, there two additional fields that deal with the type of objects returned. First, let's go into a bit more detail what setting the $className property actually does: It is the type of database object in which the rows are wrapped. It determines which database table is actually queried and which index is used (see the $databaseTableName and $databaseTableIndexName properties of DatabaseObject ). Sometimes you might use the database table of some database object but wrap the rows in another database object. This can be achieved by setting the $objectClassName property to the desired class name. In other cases, you might want to wrap the created objects in a database object decorator which can be done by setting the $decoratorClassName property to the desired class name: <? php $exampleList = new \\wcf\\data\\example\\ExampleList (); $exampleList -> decoratorClassName = \\wcf\\data\\example\\ViewableExample :: class ; Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class: <? php namespace wcf\\data\\example ; class ViewableExampleList extends ExampleList { public $decoratorClassName = ViewableExample :: class ; }","title":"DatabaseObjectList"},{"location":"php/database-objects/#abstractdatabaseobjectaction","text":"Row creation and manipulation can be performed using the aforementioned DatabaseObjectEditor class, but this approach has two major issues: Row creation, update and deletion takes place silently without notifying any other components. Data is passed to the database adapter without any further processing. The AbstractDatabaseObjectAction solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { public $className = ExampleEditor :: class ; }","title":"AbstractDatabaseObjectAction"},{"location":"php/database-objects/#executing-an-action","text":"The method AbstractDatabaseObjectAction::validateAction() is internally used for AJAX method invocation and must not be called programmatically. The next example represents the same functionality as seen for DatabaseObjectEditor : <? php use wcf\\data\\example\\ExampleAction ; // create a row $exampleAction = new ExampleAction ([], 'create' , [ 'data' => [ 'bar' => 'Hello World' ] ]); $example = $exampleAction -> executeAction ()[ 'returnValues' ]; // update a row using the id $exampleAction = new ExampleAction ([ 1 ], 'update' , [ 'data' => [ 'bar' => 'baz' ] ]); $exampleAction -> executeAction (); // delete a row using a model $exampleAction = new ExampleAction ([ $example ], 'delete' ); $exampleAction -> executeAction (); You can access the return values both by storing the return value of executeAction() or by retrieving it via getReturnValues() . Events initializeAction , validateAction and finalizeAction","title":"Executing an Action"},{"location":"php/database-objects/#custom-method-with-ajax-support","text":"This section is about adding the method baz() to ExampleAction and calling it via AJAX.","title":"Custom Method with AJAX Support"},{"location":"php/database-objects/#ajax-validation","text":"Methods of an action cannot be called via AJAX, unless they have a validation method. This means that ExampleAction must define both a public function baz() and public function validateBaz() , the name for the validation method is constructed by upper-casing the first character of the method name and prepending validate . The lack of the companion validate* method will cause the AJAX proxy to deny the request instantaneously. Do not add a validation method if you don't want it to be callable via AJAX ever!","title":"AJAX Validation"},{"location":"php/database-objects/#create-update-and-delete","text":"The methods create , update and delete are available for all classes deriving from AbstractDatabaseObjectAction and directly pass the input data to the DatabaseObjectEditor . These methods deny access to them via AJAX by default, unless you explicitly enable access. Depending on your case, there are two different strategies to enable AJAX access to them. <? php namespace wcf\\data\\example ; use wcf\\data\\AbstractDatabaseObjectAction ; class ExampleAction extends AbstractDatabaseObjectAction { // `create()` can now be called via AJAX if the requesting user posses the listed permissions protected $permissionsCreate = [ 'admin.example.canManageExample' ]; public function validateUpdate () { // your very own validation logic that does not make use of the // built-in `$permissionsUpdate` property // you can still invoke the built-in permissions check if you like to parent :: validateUpdate (); } }","title":"create, update and delete"},{"location":"php/database-objects/#allow-invokation-by-guests","text":"Invoking methods is restricted to logged-in users by default and the only way to override this behavior is to alter the property $allowGuestAccess . It is a simple string array that is expected to hold all methods that should be accessible by users, excluding their companion validation methods.","title":"Allow Invokation by Guests"},{"location":"php/database-objects/#acp-access-only","text":"Method access is usually limited by permissions, but sometimes there might be the need for some added security to avoid mistakes. The $requireACP property works similar to $allowGuestAccess , but enforces the request to originate from the ACP together with a valid ACP session, ensuring that only users able to access the ACP can actually invoke these methods.","title":"ACP Access Only"},{"location":"php/exceptions/","text":"Exceptions # SPL Exceptions # The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible. Custom Exceptions # Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Exceptions"},{"location":"php/exceptions/#exceptions","text":"","title":"Exceptions"},{"location":"php/exceptions/#spl-exceptions","text":"The Standard PHP Library (SPL) provides some exceptions that should be used whenever possible.","title":"SPL Exceptions"},{"location":"php/exceptions/#custom-exceptions","text":"Do not use wcf\\system\\exception\\SystemException anymore, use specific exception classes! The following table contains a list of custom exceptions that are commonly used. All of the exceptions are found in the wcf\\system\\exception namespace. Class name (examples) when to use IllegalLinkException access to a page that belongs to a non-existing object, executing actions on specific non-existing objects (is shown as http 404 error to the user) ImplementationException a class does not implement an expected interface InvalidObjectArgument 5.4+ API method support generic objects but specific implementation requires objects of specific (sub)class and different object is given InvalidObjectTypeException object type is not of an expected object type definition InvalidSecurityTokenException given security token does not match the security token of the active user's session ParentClassException a class does not extend an expected (parent) class PermissionDeniedException page access without permission, action execution without permission (is shown as http 403 error to the user) UserInputException user input does not pass validation","title":"Custom Exceptions"},{"location":"php/gdpr/","text":"General Data Protection Regulation (GDPR) # Introduction # The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com. Including Data in the Export # The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property. Example code # <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } } $data # Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app. $exportUserProperties # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content. $exportUserPropertiesIfNotEmpty # Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output. $exportUserOptionSettings # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content. $exportUserOptionSettingsIfNotEmpty # Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. $ipAddresses # List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported. $skipUserOptions # All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"GDPR"},{"location":"php/gdpr/#general-data-protection-regulation-gdpr","text":"","title":"General Data Protection Regulation (GDPR)"},{"location":"php/gdpr/#introduction","text":"The General Data Protection Regulation (GDPR) of the European Union enters into force on May 25, 2018. It comes with a set of restrictions when handling users' personal data as well as to provide an interface to export this data on demand. If you're looking for a guide on the implications of the GDPR and what you will need or consider to do, please read the article Implementation of the GDPR on woltlab.com.","title":"Introduction"},{"location":"php/gdpr/#including-data-in-the-export","text":"The wcf\\acp\\action\\UserExportGdprAction introduced with WoltLab Suite 3.1.3 already includes the Core itself as well as all official apps, but you'll need to include any personal data stored for your plugin or app by yourself. The event export is fired before any data is sent out, but after any Core data has been dumped to the $data property.","title":"Including Data in the Export"},{"location":"php/gdpr/#example-code","text":"<? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\action\\UserExportGdprAction ; use wcf\\data\\user\\UserProfile ; class MyUserExportGdprActionListener implements IParameterizedEventListener { public function execute ( /** @var UserExportGdprAction $eventObj */ $eventObj , $className , $eventName , array & $parameters ) { /** @var UserProfile $user */ $user = $eventObj -> user ; $eventObj -> data [ 'my.fancy.plugin' ] = [ 'superPersonalData' => \"This text is super personal and should be included in the output\" , 'weirdIpAddresses' => $eventObj -> exportIpAddresses ( 'app' . WCF_N . '_non_standard_column_names_for_ip_addresses' , 'ipAddressColumnName' , 'timeColumnName' , 'userIDColumnName' ) ]; $eventObj -> exportUserProperties [] = 'shouldAlwaysExportThisField' ; $eventObj -> exportUserPropertiesIfNotEmpty [] = 'myFancyField' ; $eventObj -> exportUserOptionSettings [] = 'thisSettingIsAlwaysExported' ; $eventObj -> exportUserOptionSettingsIfNotEmpty [] = 'someSettingContainingPersonalData' ; $eventObj -> ipAddresses [ 'my.fancy.plugin' ] = [ 'wcf' . WCF_N . '_my_fancy_table' , 'wcf' . WCF_N . '_i_also_store_ipaddresses_here' ]; $eventObj -> skipUserOptions [] = 'thisLooksLikePersonalDataButItIsNot' ; $eventObj -> skipUserOptions [] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt' ; } }","title":"Example code"},{"location":"php/gdpr/#data","text":"Contains the entire data that will be included in the exported JSON file, some fields may already exist (such as 'com.woltlab.wcf' ) and while you may add or edit any fields within, you should restrict yourself to only append data from your plugin or app.","title":"$data"},{"location":"php/gdpr/#exportuserproperties","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. The listed properties will always be included regardless of their content.","title":"$exportUserProperties"},{"location":"php/gdpr/#exportuserpropertiesifnotempty","text":"Only a whitelist of columns in wcfN_user is exported by default, if your plugin or app adds one or more columns to this table that do hold personal data, then you will have to append it to this array. Empty values will not be added to the output.","title":"$exportUserPropertiesIfNotEmpty"},{"location":"php/gdpr/#exportuseroptionsettings","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data. The listed settings are always included regardless of their content.","title":"$exportUserOptionSettings"},{"location":"php/gdpr/#exportuseroptionsettingsifnotempty","text":"Any user option that exists within a settings.* category is automatically excluded from the export, with the notable exception of the timezone option. You can opt-in to include your setting by appending to this array, if it contains any personal data.","title":"$exportUserOptionSettingsIfNotEmpty"},{"location":"php/gdpr/#ipaddresses","text":"List of database table names per package identifier that contain ip addresses. The contained ip addresses will be exported when the ip logging module is enabled. It expects the database table to use the column names ipAddress , time and userID . If your table does not match this pattern for whatever reason, you'll need to manually probe for LOG_IP_ADDRESS and then call exportIpAddresses() to retrieve the list. Afterwards you are responsible to append these ip addresses to the $data array to have it exported.","title":"$ipAddresses"},{"location":"php/gdpr/#skipuseroptions","text":"All user options are included in the export by default, unless they start with can* or admin* , or are blacklisted using this array. You should append any of your plugin's or app's user option that should not be exported, for example because it does not contain personal data, such as internal data.","title":"$skipUserOptions"},{"location":"php/pages/","text":"Page Types # AbstractPage # The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow. Method Chain # __run() # This is the only method being invoked from the outside and starts the whole chain. readParameters() # Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters show() # Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show readData() # Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData assignVariables() # Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables AbstractForm # Extends the AbstractPage implementation with additional methods designed to handle form submissions properly. Method Chain # __run() # Inherited from AbstractPage. readParameters() # Inherited from AbstractPage. show() # Inherited from AbstractPage. submit() # The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit readFormParameters() # This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters validate() # Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate save() # Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save saved() # This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved readData() # Inherited from AbstractPage. assignVariables() # Inherited from AbstractPage.","title":"Pages"},{"location":"php/pages/#page-types","text":"","title":"Page Types"},{"location":"php/pages/#abstractpage","text":"The default implementation for pages to present any sort of content, but are designed to handle GET requests only. They usually follow a fixed method chain that will be invoked one after another, adding logical sections to the request flow.","title":"AbstractPage"},{"location":"php/pages/#method-chain","text":"","title":"Method Chain"},{"location":"php/pages/#__run","text":"This is the only method being invoked from the outside and starts the whole chain.","title":"__run()"},{"location":"php/pages/#readparameters","text":"Reads and sanitizes request parameters, this should be the only method to ever read user-supplied input. Read data should be stored in class properties to be accessible at a later point, allowing your code to safely assume that the data has been sanitized and is safe to work with. A typical example is the board page from the forum app that reads the id and attempts to identify the request forum. public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> boardID = intval ( $_REQUEST [ 'id' ]); $this -> board = BoardCache :: getInstance () -> getBoard ( $this -> boardID ); if ( $this -> board === null ) { throw new IllegalLinkException (); } // check permissions if ( ! $this -> board -> canEnter ()) { throw new PermissionDeniedException (); } } Events readParameters","title":"readParameters()"},{"location":"php/pages/#show","text":"Used to be the method of choice to handle permissions and module option checks, but has been used almost entirely as an internal method since the introduction of the properties $loginRequired , $neededModules and $neededPermissions . Events checkModules , checkPermissions and show","title":"show()"},{"location":"php/pages/#readdata","text":"Central method for data retrieval based on class properties including those populated with user data in readParameters() . It is strongly recommended to use this method to read data in order to properly separate the business logic present in your class. Events readData","title":"readData()"},{"location":"php/pages/#assignvariables","text":"Last method call before the template engine kicks in and renders the template. All though some properties are bound to the template automatically, you still need to pass any custom variables and class properties to the engine to make them available in templates. Following the example in readParameters() , the code below adds the board data to the template. public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'board' => $this -> board , 'boardID' => $this -> boardID ]); } Events assignVariables","title":"assignVariables()"},{"location":"php/pages/#abstractform","text":"Extends the AbstractPage implementation with additional methods designed to handle form submissions properly.","title":"AbstractForm"},{"location":"php/pages/#method-chain_1","text":"","title":"Method Chain"},{"location":"php/pages/#__run_1","text":"Inherited from AbstractPage.","title":"__run()"},{"location":"php/pages/#readparameters_1","text":"Inherited from AbstractPage.","title":"readParameters()"},{"location":"php/pages/#show_1","text":"Inherited from AbstractPage.","title":"show()"},{"location":"php/pages/#submit","text":"The methods submit() up until save() are only invoked if either $_POST or $_FILES are not empty, otherwise they won't be invoked and the execution will continue with readData() . This is an internal method that is responsible of input processing and validation. Events submit","title":"submit()"},{"location":"php/pages/#readformparameters","text":"This method is quite similar to readParameters() that is being called earlier, but is designed around reading form data submitted through POST requests. You should avoid accessing $_GET or $_REQUEST in this context to avoid mixing up parameters evaluated when retrieving the page on first load and when submitting to it. Events readFormParameters","title":"readFormParameters()"},{"location":"php/pages/#validate","text":"Deals with input validation and automatically catches exceptions deriving from wcf\\system\\exception\\UserInputException , resulting in a clean and consistent error handling for the user. Events validate","title":"validate()"},{"location":"php/pages/#save","text":"Saves the processed data to database or any other source of your choice. Please keep in mind to invoke $this->saved() before resetting the form data. Events save","title":"save()"},{"location":"php/pages/#saved","text":"This method is not called automatically and must be invoked manually by executing $this->saved() inside save() . The only purpose of this method is to fire the event saved that signals that the form data has been processed successfully and data has been saved. It is somewhat special as it is dispatched after the data has been saved, but before the data is purged during form reset. This is by default the last event that has access to the processed data. Events saved","title":"saved()"},{"location":"php/pages/#readdata_1","text":"Inherited from AbstractPage.","title":"readData()"},{"location":"php/pages/#assignvariables_1","text":"Inherited from AbstractPage.","title":"assignVariables()"},{"location":"php/api/caches/","text":"Caches # WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request. Understanding Caching # Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem. When to Use a Cache # It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild. When not to Use a Cache # I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"Caches"},{"location":"php/api/caches/#caches","text":"WoltLab Suite offers two distinct types of caches: Persistent caches created by cache builders whose data can be stored using different cache sources. Runtime caches store objects for the duration of a single request.","title":"Caches"},{"location":"php/api/caches/#understanding-caching","text":"Every so often, plugins make use of cache builders or runtime caches to store their data, even if there is absolutely no need for them to do so. Usually, this involves a strong opinion about the total number of SQL queries on a page, including but not limited to some magic treshold numbers, which should not be exceeded for \"performance reasons\". This misconception can easily lead into thinking that SQL queries should be avoided or at least written to a cache, so that it doesn't need to be executed so often. Unfortunately, this completely ignores the fact that both a single query can take down your app (e. g. full table scan on millions of rows), but 10 queries using a primary key on a table with a few hundred rows will not slow down your page. There are some queries that should go into caches by design, but most of the cache builders weren't initially there, but instead have been added because they were required to reduce the load significantly . You need to understand that caches always come at a cost, even a runtime cache does! In particular, they will always consume memory that is not released over the duration of the request lifecycle and potentially even leak memory by holding references to objects and data structures that are no longer required. Caching should always be a solution for a problem.","title":"Understanding Caching"},{"location":"php/api/caches/#when-to-use-a-cache","text":"It's difficult to provide a definite answer or checklist when to use a cache and why it is required at this point, because the answer is: It depends. The permission cache for user groups is a good example for a valid cache, where we can achieve significant performance improvement compared to processing this data on every request. Its caches are build for each permutation of user group memberships that are encountered for a page request. Building this data is an expensive process that involves both inheritance and specific rules in regards to when a value for a permission overrules another value. The added benefit of this cache is that one cache usually serves a large number of users with the same group memberships and by computing these permissions once, we can serve many different requests. Also, the permissions are rather static values that change very rarely and thus we can expect a very high cache lifetime before it gets rebuild.","title":"When to Use a Cache"},{"location":"php/api/caches/#when-not-to-use-a-cache","text":"I remember, a few years ago, there was a plugin that displayed a user's character from an online video game. The character sheet not only included a list of basic statistics, but also displayed the items that this character was wearing and or holding at the time. The data for these items were downloaded in bulk from the game's vendor servers and stored in a persistent cache file that periodically gets renewed. There is nothing wrong with the idea of caching the data on your own server rather than requesting them everytime from the vendor's servers - not only because they imposed a limit on the number of requests per hour. Unfortunately, the character sheet had a sub-par performance and the users were upset by the significant loading times compared to literally every other page on the same server. The author of the plugin was working hard to resolve this issue and was evaluating all kind of methods to improve the page performance, including deep-diving into the realm of micro-optimizations to squeeze out every last bit of performance that is possible. The real problem was the cache file itself, it turns out that it was holding the data for several thousand items with a total file size of about 13 megabytes. It doesn't look that much at first glance, after all this isn't the '90s anymore, but unserializing a 13 megabyte array is really slow and looking up items in such a large array isn't exactly fast either. The solution was rather simple, the data that was fetched from the vendor's API was instead written into a separate database table. Next, the persistent cache was removed and the character sheet would now request the item data for that specific character straight from the database. Previously, the character sheet took several seconds to load and after the change it was done in a fraction of a second. Although quite extreme, this illustrates a situation where the cache file was introduced in the design process, without evaluating if the cache - at least how it was implemented - was really necessary. Caching should always be a solution for a problem. Not the other way around.","title":"When not to Use a Cache"},{"location":"php/api/caches_persistent-caches/","text":"Persistent Caches # Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision. AbstractCacheBuilder # Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters ); getData(array $parameters = [], string $arrayIndex = ''): array # Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead. getMaxLifetime(): int # Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated. reset(array $parameters = []): void # Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() . rebuild(array $parameters): array # This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#persistent-caches","text":"Relational databases are designed around the principle of normalized data that is organized across clearly separated tables with defined releations between data rows. While this enables you to quickly access and modify individual rows and columns, it can create the problem that re-assembling this data into a more complex structure can be quite expensive. For example, the user group permissions are stored for each user group and each permissions separately, but in order to be applied, they need to be fetched and the cumulative values across all user groups of an user have to be calculated. These repetitive tasks on barely ever changing data make them an excellent target for caching, where all sub-sequent requests are accelerated because they no longer have to perform the same expensive calculations every time. It is easy to get lost in the realm of caching, especially when it comes to the decision if you should use a cache or not. When in doubt, you should opt to not use them, because they also come at a hidden cost that cannot be expressed through simple SQL query counts. If you haven't already, it is recommended that you read the introduction article on caching first, it provides a bit of background on caches and examples that should help you in your decision.","title":"Persistent Caches"},{"location":"php/api/caches_persistent-caches/#abstractcachebuilder","text":"Every cache builder should derive from the base class AbstractCacheBuilder that already implements the mandatory interface ICacheBuilder . <? php namespace wcf\\system\\cache\\builder ; class ExampleCacheBuilder extends AbstractCacheBuilder { // 3600 = 1hr protected $maxLifetime = 3600 ; public function rebuild ( array $parameters ) { $data = []; // fetch and process your data and assign it to `$data` return $data ; } } Reading data from your cache builder is quite simple and follows a consistent pattern. The callee only needs to know the name of the cache builder, which parameters it requires and how the returned data looks like. It does not need to know how the data is retrieve, where it was stored, nor if it had to be rebuild due to the maximum lifetime. <? php use wcf\\system\\cache\\builder\\ExampleCacheBuilder ; $data = ExampleCacheBuilder :: getInstance () -> getData ( $parameters );","title":"AbstractCacheBuilder"},{"location":"php/api/caches_persistent-caches/#getdataarray-parameters-string-arrayindex-array","text":"Retrieves the data from the cache builder, the $parameters array is automatically sorted to allow sub-sequent requests for the same parameters to be recognized, even if their parameters are mixed. For example, getData([1, 2]) and getData([2, 1]) will have the same exact result. The optional $arrayIndex will instruct the cache builder to retrieve the data and examine if the returned data is an array that has the index $arrayIndex . If it is set, the potion below this index is returned instead.","title":"getData(array $parameters = [], string $arrayIndex = ''): array"},{"location":"php/api/caches_persistent-caches/#getmaxlifetime-int","text":"Returns the maximum lifetime of a cache in seconds. It can be controlled through the protected $maxLifetime property which defaults to 0 . Any cache that has a lifetime greater than 0 is automatically discarded when exceeding this age, otherwise it will remain forever until it is explicitly removed or invalidated.","title":"getMaxLifetime(): int"},{"location":"php/api/caches_persistent-caches/#resetarray-parameters-void","text":"Invalidates a cache, the $parameters array will again be ordered using the same rules that are applied for getData() .","title":"reset(array $parameters = []): void"},{"location":"php/api/caches_persistent-caches/#rebuildarray-parameters-array","text":"This method is protected. This is the only method that a cache builder deriving from AbstractCacheBuilder has to implement and it will be invoked whenever the cache is required to be rebuild for whatever reason.","title":"rebuild(array $parameters): array"},{"location":"php/api/caches_runtime-caches/","text":"Runtime Caches # Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again. IRuntimeCache # Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ). Usage # <? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]); Example # <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#runtime-caches","text":"Runtime caches store objects created during the runtime of the script and are automatically discarded after the script terminates. Runtime caches are especially useful when objects are fetched by different APIs, each requiring separate requests. By using a runtime cache, you have two advantages: If the API allows it, you can delay fetching the actual objects and initially only tell the runtime cache that at some point in the future of the current request, you need the objects with the given ids. If multiple APIs do this one after another, all objects can be fetched using only one query instead of each API querying the database on its own. If an object with the same ID has already been fetched from database, this object is simply returned and can be reused instead of being fetched from database again.","title":"Runtime Caches"},{"location":"php/api/caches_runtime-caches/#iruntimecache","text":"Every runtime cache has to implement the IRuntimeCache interface. It is recommended, however, that you extend AbstractRuntimeCache , the default implementation of the runtime cache interface. In most instances, you only need to set the AbstractRuntimeCache::$listClassName property to the name of database object list class which fetches the cached objects from database (see example ).","title":"IRuntimeCache"},{"location":"php/api/caches_runtime-caches/#usage","text":"<? php use wcf\\system\\cache\\runtime\\UserRuntimeCache ; $userIDs = [ 1 , 2 ]; // first (optional) step: tell runtime cache to remember user ids UserRuntimeCache :: getInstance () -> cacheObjectIDs ( $userIDs ); // [\u2026] // second step: fetch the objects from database $users = UserRuntimeCache :: getInstance () -> getObjects ( $userIDs ); // somewhere else: fetch only one user $userID = 1 ; UserRuntimeCache :: getInstance () -> cacheObjectID ( $userID ); // [\u2026] // get user without the cache actually fetching it from database because it has already been loaded $user = UserRuntimeCache :: getInstance () -> getObject ( $userID ); // somewhere else: fetch users directly without caching user ids first $users = UserRuntimeCache :: getInstance () -> getObjects ([ 3 , 4 ]);","title":"Usage"},{"location":"php/api/caches_runtime-caches/#example","text":"<? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\user\\User ; use wcf\\data\\user\\UserList ; /** * Runtime cache implementation for users. * * @author Matthias Schmidt * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method User[] getCachedObjects() * @method User getObject($objectID) * @method User[] getObjects(array $objectIDs) */ class UserRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = UserList :: class ; }","title":"Example"},{"location":"php/api/comments/","text":"Comments # User Group Options # You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above. Language Items # User Group Options # The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"Comments"},{"location":"php/api/comments/#comments","text":"","title":"Comments"},{"location":"php/api/comments/#user-group-options","text":"You need to create the following permissions: user group type permission type naming user creating comments user.foo.canAddComment user editing own comments user.foo.canEditComment user deleting own comments user.foo.canDeleteComment moderator moderating comments mod.foo.canModerateComment moderator editing comments mod.foo.canEditComment moderator deleting comments mod.foo.canDeleteComment Within their respective user group option category, the options should be listed in the same order as in the table above.","title":"User Group Options"},{"location":"php/api/comments/#language-items","text":"","title":"Language Items"},{"location":"php/api/comments/#user-group-options_1","text":"The language items for the comment-related user group options generally have the same values: wcf.acp.group.option.user.foo.canAddComment German: Kann Kommentare erstellen English: Can create comments wcf.acp.group.option.user.foo.canEditComment German: Kann eigene Kommentare bearbeiten English: Can edit their comments wcf.acp.group.option.user.foo.canDeleteComment German: Kann eigene Kommentare l\u00f6schen English: Can delete their comments wcf.acp.group.option.mod.foo.canModerateComment German: Kann Kommentare moderieren English: Can moderate comments wcf.acp.group.option.mod.foo.canEditComment German: Kann Kommentare bearbeiten English: Can edit comments wcf.acp.group.option.mod.foo.canDeleteComment German: Kann Kommentare l\u00f6schen English: Can delete comments","title":"User Group Options"},{"location":"php/api/cronjobs/","text":"Cronjobs # Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob. Example # <? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } } ICronjob Interface # Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ). Executing Cronjobs Through CLI # Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Cronjobs"},{"location":"php/api/cronjobs/#cronjobs","text":"Cronjobs offer an easy way to execute actions periodically, like cleaning up the database. The execution of cronjobs is not guaranteed but requires someone to access the page with JavaScript enabled. This page focuses on the technical aspects of cronjobs, the cronjob package installation plugin page covers how you can actually register a cronjob.","title":"Cronjobs"},{"location":"php/api/cronjobs/#example","text":"<? php namespace wcf\\system\\cronjob ; use wcf\\data\\cronjob\\Cronjob ; use wcf\\system\\WCF ; /** * Updates the last activity timestamp in the user table. * * @author Marcel Werk * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cronjob */ class LastActivityCronjob extends AbstractCronjob { /** * @inheritDoc */ public function execute ( Cronjob $cronjob ) { parent :: execute ( $cronjob ); $sql = \"UPDATE wcf\" . WCF_N . \"_user user_table, wcf\" . WCF_N . \"_session session SET user_table.lastActivityTime = session.lastActivityTime WHERE user_table.userID = session.userID AND session.userID <> 0\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); $statement -> execute (); } }","title":"Example"},{"location":"php/api/cronjobs/#icronjob-interface","text":"Every cronjob needs to implement the wcf\\system\\cronjob\\ICronjob interface which requires the execute(Cronjob $cronjob) method to be implemented. This method is called by wcf\\system\\cronjob\\CronjobScheduler when executing the cronjobs. In practice, however, you should extend the AbstractCronjob class and also call the AbstractCronjob::execute() method as it fires an event which makes cronjobs extendable by plugins (see event documentation ).","title":"ICronjob Interface"},{"location":"php/api/cronjobs/#executing-cronjobs-through-cli","text":"Cronjobs can be executed through the command-line interface (CLI): php /path/to/wcf/cli.php << 'EOT' USERNAME PASSWORD cronjob execute EOT","title":"Executing Cronjobs Through CLI"},{"location":"php/api/event_list/","text":"Event List # Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name. WoltLab Suite Core # Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput * WoltLab Suite Forum # Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"Event List"},{"location":"php/api/event_list/#event-list","text":"Events whose name is marked with an asterisk are called from a static method and thus do not provide any object, just the class name.","title":"Event List"},{"location":"php/api/event_list/#woltlab-suite-core","text":"Class Event Name wcf\\acp\\action\\UserExportGdprAction export wcf\\acp\\form\\StyleAddForm setVariables wcf\\acp\\form\\UserSearchForm search wcf\\action\\AbstractAction checkModules wcf\\action\\AbstractAction checkPermissions wcf\\action\\AbstractAction execute wcf\\action\\AbstractAction executed wcf\\action\\AbstractAction readParameters wcf\\data\\attachment\\AttachmentAction generateThumbnail wcf\\data\\session\\SessionAction keepAlive wcf\\data\\session\\SessionAction poll wcf\\data\\trophy\\Trophy renderTrophy wcf\\data\\user\\online\\UserOnline getBrowser wcf\\data\\user\\online\\UserOnlineList isVisible wcf\\data\\user\\trophy\\UserTrophy getReplacements wcf\\data\\user\\UserAction beforeFindUsers wcf\\data\\user\\UserAction rename wcf\\data\\user\\UserProfile getAvatar wcf\\data\\user\\UserProfile isAccessible wcf\\data\\AbstractDatabaseObjectAction finalizeAction wcf\\data\\AbstractDatabaseObjectAction initializeAction wcf\\data\\AbstractDatabaseObjectAction validateAction wcf\\data\\DatabaseObjectList init wcf\\form\\AbstractForm readFormParameters wcf\\form\\AbstractForm save wcf\\form\\AbstractForm saved wcf\\form\\AbstractForm submit wcf\\form\\AbstractForm validate wcf\\form\\AbstractModerationForm prepareSave wcf\\page\\AbstractPage assignVariables wcf\\page\\AbstractPage checkModules wcf\\page\\AbstractPage checkPermissions wcf\\page\\AbstractPage readData wcf\\page\\AbstractPage readParameters wcf\\page\\AbstractPage show wcf\\page\\MultipleLinkPage beforeReadObjects wcf\\page\\MultipleLinkPage calculateNumberOfPages wcf\\page\\MultipleLinkPage countItems wcf\\page\\SortablePage validateSortField wcf\\page\\SortablePage validateSortOrder wcf\\system\\bbcode\\MessageParser afterParsing wcf\\system\\bbcode\\MessageParser beforeParsing wcf\\system\\bbcode\\SimpleMessageParser afterParsing wcf\\system\\bbcode\\SimpleMessageParser beforeParsing wcf\\system\\box\\AbstractBoxController __construct wcf\\system\\box\\AbstractBoxController afterLoadContent wcf\\system\\box\\AbstractBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController afterLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController beforeLoadContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController hasContent wcf\\system\\box\\AbstractDatabaseObjectListBoxController readObjects wcf\\system\\cronjob\\AbstractCronjob execute wcf\\system\\email\\Email getJobs wcf\\system\\form\\builder\\container\\wysiwyg\\WysiwygFormContainer populate wcf\\system\\html\\input\\filter\\MessageHtmlInputFilter setAttributeDefinitions wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor afterProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeEmbeddedProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor beforeProcess wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor convertPlainLinks wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor getTextContent wcf\\system\\html\\input\\node\\HtmlInputNodeProcessor parseEmbeddedContent wcf\\system\\html\\input\\node\\HtmlInputNodeWoltlabMetacodeMarker filterGroups wcf\\system\\html\\output\\node\\HtmlOutputNodePre selectHighlighter wcf\\system\\html\\output\\node\\HtmlOutputNodeProcessor beforeProcess wcf\\system\\image\\adapter\\ImagickImageAdapter getResizeFilter wcf\\system\\menu\\user\\profile\\UserProfileMenu init wcf\\system\\menu\\user\\profile\\UserProfileMenu loadCache wcf\\system\\menu\\TreeMenu init wcf\\system\\menu\\TreeMenu loadCache wcf\\system\\message\\QuickReplyManager addFullQuote wcf\\system\\message\\QuickReplyManager allowedDataParameters wcf\\system\\message\\QuickReplyManager beforeRenderQuote wcf\\system\\message\\QuickReplyManager createMessage wcf\\system\\message\\QuickReplyManager createdMessage wcf\\system\\message\\QuickReplyManager getMessage wcf\\system\\message\\QuickReplyManager validateParameters wcf\\system\\option\\OptionHandler afterReadCache wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin construct wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin hasUninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin install wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin uninstall wcf\\system\\package\\plugin\\AbstractPackageInstallationPlugin update wcf\\system\\package\\plugin\\ObjectTypePackageInstallationPlugin addConditionFields wcf\\system\\package\\PackageInstallationDispatcher postInstall wcf\\system\\package\\PackageUninstallationDispatcher postUninstall wcf\\system\\reaction\\ReactionHandler getDataAttributes wcf\\system\\request\\RouteHandler didInit wcf\\system\\session\\ACPSessionFactory afterInit wcf\\system\\session\\ACPSessionFactory beforeInit wcf\\system\\session\\SessionHandler afterChangeUser wcf\\system\\session\\SessionHandler beforeChangeUser wcf\\system\\style\\StyleCompiler compile wcf\\system\\template\\TemplateEngine afterDisplay wcf\\system\\template\\TemplateEngine beforeDisplay wcf\\system\\upload\\DefaultUploadFileSaveStrategy generateThumbnails wcf\\system\\upload\\DefaultUploadFileSaveStrategy save wcf\\system\\user\\authentication\\UserAuthenticationFactory init wcf\\system\\user\\notification\\UserNotificationHandler createdNotification wcf\\system\\user\\notification\\UserNotificationHandler fireEvent wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmed wcf\\system\\user\\notification\\UserNotificationHandler markAsConfirmedByIDs wcf\\system\\user\\notification\\UserNotificationHandler removeNotifications wcf\\system\\user\\notification\\UserNotificationHandler updateTriggerCount wcf\\system\\user\\UserBirthdayCache loadMonth wcf\\system\\worker\\AbstractRebuildDataWorker execute wcf\\system\\CLIWCF afterArgumentParsing wcf\\system\\CLIWCF beforeArgumentParsing wcf\\system\\WCF initialized wcf\\util\\HeaderUtil parseOutput *","title":"WoltLab Suite Core"},{"location":"php/api/event_list/#woltlab-suite-forum","text":"Class Event Name wbb\\data\\board\\BoardAction cloneBoard wbb\\data\\post\\PostAction quickReplyShouldMerge wbb\\system\\thread\\ThreadHandler didInit","title":"WoltLab Suite Forum"},{"location":"php/api/events/","text":"Events # WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here . Introductory Example # Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled. Listening to Events # In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty. Firing Events # If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards. Example: Using $parameters argument # Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } } Advanced Example: Additional Form Field # One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Events"},{"location":"php/api/events/#events","text":"WoltLab Suite's event system allows manipulation of program flows and data without having to change any of the original source code. At many locations throughout the PHP code of WoltLab Suite Core and mainly through inheritance also in the applications and plugins, so called events are fired which trigger registered event listeners that get access to the object firing the event (or at least the class name if the event has been fired in a static method). This page focuses on the technical aspects of events and event listeners, the eventListener package installation plugin page covers how you can actually register an event listener. A comprehensive list of all available events is provided here .","title":"Events"},{"location":"php/api/events/#introductory-example","text":"Let's start with a simple example to illustrate how the event system works. Consider this pre-existing class: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleComponent { public $var = 1 ; public function getVar () { EventHandler :: getInstance () -> fireAction ( $this , 'getVar' ); return $this -> var ; } } where an event with event name getVar is fired in the getVar() method. If you create an object of this class and call the getVar() method, the return value will be 1 , of course: <? php $example = new wcf\\system\\example\\ExampleComponent (); if ( $example -> getVar () == 1 ) { echo \"var is 1!\" ; } else if ( $example -> getVar () == 2 ) { echo \"var is 2!\" ; } else { echo \"No, var is neither 1 nor 2.\" ; } // output: var is 1! Now, consider that we have registered the following event listener to this event: <? php namespace wcf\\system\\event\\listener ; class ExampleEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $eventObj -> var = 2 ; } } Whenever the event in the getVar() method is called, this method (of the same event listener object) is called. In this case, the value of the method's first parameter is the ExampleComponent object passed as the first argument of the EventHandler::fireAction() call in ExampleComponent::getVar() . As ExampleComponent::$var is a public property, the event listener code can change it and set it to 2 . If you now execute the example code from above again, the output will change from var is 1! to var is 2! because prior to returning the value, the event listener code changes the value from 1 to 2 . This introductory example illustrates how event listeners can change data in a non-intrusive way. Program flow can be changed, for example, by throwing a wcf\\system\\exception\\PermissionDeniedException if some additional constraint to access a page is not fulfilled.","title":"Introductory Example"},{"location":"php/api/events/#listening-to-events","text":"In order to listen to events, you need to register the event listener and the event listener itself needs to implement the interface wcf\\system\\event\\listener\\IParameterizedEventListener which only contains the execute method (see example above). The first parameter $eventObj of the method contains the passed object where the event is fired or the name of the class in which the event is fired if it is fired from a static method. The second parameter $className always contains the name of the class where the event has been fired. The third parameter $eventName provides the name of the event within a class to uniquely identify the exact location in the class where the event has been fired. The last parameter $parameters is a reference to the array which contains additional data passed by the method firing the event. If no additional data is passed, $parameters is empty.","title":"Listening to Events"},{"location":"php/api/events/#firing-events","text":"If you write code and want plugins to have access at certain points, you can fire an event on your own. The only thing to do is to call the wcf\\system\\event\\EventHandler::fireAction($eventObj, $eventName, array &$parameters = []) method and pass the following parameters: $eventObj should be $this if you fire from an object context, otherwise pass the class name static::class . $eventName identifies the event within the class and generally has the same name as the method. In cases, were you might fire more than one event in a method, for example before and after a certain piece of code, you can use the prefixes before* and after* in your event names. $parameters is an optional array which allows you to pass additional data to the event listeners without having to make this data accessible via a property explicitly only created for this purpose. This additional data can either be just additional information for the event listeners about the context of the method call or allow the event listener to manipulate local data if the code, where the event has been fired, uses the passed data afterwards.","title":"Firing Events"},{"location":"php/api/events/#example-using-parameters-argument","text":"Consider the following method which gets some text that the methods parses. <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'parse' , $parameters ); return $parameters [ 'text' ]; } } After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter. Then, a plugin can deliver the following event listener <? php namespace wcf\\system\\event\\listener ; class ExampleParserEventListener implements IParameterizedEventListener { public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $text = $parameters [ 'text' ]; // [some additional parsing which changes $text] $parameters [ 'text' ] = $text ; } } which can access the text via $parameters['text'] . This example can also be perfectly used to illustrate how to name multiple events in the same method. Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events: <? php namespace wcf\\system\\example ; use wcf\\system\\event\\EventHandler ; class ExampleParser { public function parse ( $text ) { $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'beforeParsing' , $parameters ); $text = $parameters [ 'text' ]; // [some parsing done by default] $parameters = [ 'text' => $text ]; EventHandler :: getInstance () -> fireAction ( $this , 'afterParsing' , $parameters ); return $parameters [ 'text' ]; } }","title":"Example: Using $parameters argument"},{"location":"php/api/events/#advanced-example-additional-form-field","text":"One common reason to use event listeners is to add an additional field to a pre-existing form (in combination with template listeners, which we will not cover here). We will assume that users are able to do both, create and edit the objects via this form. The points in the program flow of AbstractForm that are relevant here are: adding object (after the form has been submitted): reading the value of the field validating the read value saving the additional value after successful validation and resetting locally stored value or assigning the current value of the field to the template after unsuccessful validation editing object: on initial form request: reading the pre-existing value of the edited object assigning the field value to the template after the form has been submitted: reading the value of the field validating the read value saving the additional value after successful validation assigning the current value of the field to the template All of these cases can be covered the by following code in which we assume that wcf\\form\\ExampleAddForm is the form to create example objects and that wcf\\form\\ExampleEditForm extends wcf\\form\\ExampleAddForm and is used for editing existing example objects. <? php namespace wcf\\system\\event\\listener ; use wcf\\form\\ExampleAddForm ; use wcf\\form\\ExampleEditForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; class ExampleAddFormListener implements IParameterizedEventListener { protected $var = 0 ; public function execute ( $eventObj , $className , $eventName , array & $parameters ) { $this -> $eventName ( $eventObj ); } protected function assignVariables () { WCF :: getTPL () -> assign ( 'var' , $this -> var ); } protected function readData ( ExampleEditForm $eventObj ) { if ( empty ( $_POST )) { $this -> var = $eventObj -> example -> var ; } } protected function readFormParameters () { if ( isset ( $_POST [ 'var' ])) $this -> var = intval ( $_POST [ 'var' ]); } protected function save ( ExampleAddForm $eventObj ) { $eventObj -> additionalFields = array_merge ( $eventObj -> additionalFields , [ 'var' => $this -> var ]); } protected function saved () { $this -> var = 0 ; } protected function validate () { if ( $this -> var < 0 ) { throw new UserInputException ( 'var' , 'isNegative' ); } } } The execute method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself. The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used. Furthermore, the type-hinting of the parameter illustrates in which contexts the method is actually called which will become clear in the following discussion of the individual methods: assignVariables() is called for the add and the edit form and simply assigns the current value of the variable to the template. readData() reads the pre-existing value of $var if the form has not been submitted and thus is only relevant when editing objects which is illustrated by the explicit type-hint of ExampleEditForm . readFormParameters() reads the value for both, the add and the edit form. save() is, of course, also relevant in both cases but requires the form object to store the additional value in the wcf\\form\\AbstractForm::$additionalFields array which can be used if a var column has been added to the database table in which the example objects are stored. saved() is only called for the add form as it clears the internal value so that in the assignVariables() call, the default value will be assigned to the template to create an \"empty\" form. During edits, this current value is the actual value that should be shown. validate() also needs to be called in both cases as the input data always has to be validated. Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on the eventListener package installation plugin page ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/eventListener.xsd\" > <import> <eventlistener name= \"exampleAddInherited\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"exampleAdd\" > <eventclassname> wcf\\form\\ExampleAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"exampleEdit\" > <eventclassname> wcf\\form\\ExampleEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\ExampleAddFormListener </listenerclassname> </eventlistener> </import> </data>","title":"Advanced Example: Additional Form Field"},{"location":"php/api/package_installation_plugins/","text":"Package Installation Plugins # A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation. AbstractPackageInstallationPlugin # Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes. Class Members # Package Installation Plugins have a few notable class members easing your work: $installation # This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() . $application # Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored. AbstractXMLPackageInstallationPlugin # AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data. Class Members # $className # Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects. $tagName # Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object. prepareImport(array $data) # The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ]; validateImport(array $data) # The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid. findExistingItem(array $data) # The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query. 2.5.3. Example # <? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ]; handleDelete(array $items) # The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); } postImport() # Allows you to (optionally) run additionally actions after all elements were processed. AbstractOptionPackageInstallationPlugin # AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options Differences to AbstractXMLPackageInstallationPlugin # $reservedTags # $reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#package-installation-plugins","text":"A package installation plugin (PIP) defines the behavior to handle a specific instruction during package installation, update or uninstallation.","title":"Package Installation Plugins"},{"location":"php/api/package_installation_plugins/#abstractpackageinstallationplugin","text":"Any package installation plugin has to implement the IPackageInstallationPlugin interface. It is recommended however, to extend the abstract implementation AbstractPackageInstallationPlugin of this interface instead of directly implementing the interface. The abstract implementation will always provide sane methods in case of any API changes.","title":"AbstractPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members","text":"Package Installation Plugins have a few notable class members easing your work:","title":"Class Members"},{"location":"php/api/package_installation_plugins/#installation","text":"This member contains an instance of PackageInstallationDispatcher which provides you with all meta data related to the current package being processed. The most common usage is the retrieval of the package ID via $this->installation->getPackageID() .","title":"$installation"},{"location":"php/api/package_installation_plugins/#application","text":"Represents the abbreviation of the target application, e.g. wbb (default value: wcf ), used for the name of database table in which the installed data is stored.","title":"$application"},{"location":"php/api/package_installation_plugins/#abstractxmlpackageinstallationplugin","text":"AbstractPackageInstallationPlugin is the default implementation for all package installation plugins based upon a single XML document. It handles the evaluation of the document and provide you an object-orientated approach to handle its data.","title":"AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#class-members_1","text":"","title":"Class Members"},{"location":"php/api/package_installation_plugins/#classname","text":"Value must be the qualified name of a class deriving from DatabaseObjectEditor which is used to create and update objects.","title":"$className"},{"location":"php/api/package_installation_plugins/#tagname","text":"Specifies the tag name within a <import> or <delete> section of the XML document used for each installed object.","title":"$tagName"},{"location":"php/api/package_installation_plugins/#prepareimportarray-data","text":"The passed array $data contains the parsed value from each evaluated tag in the <import> section: $data['elements'] contains a list of tag names and their value. $data['attributes'] contains a list of attributes present on the tag identified by $tagName . This method should return an one-dimensional array, where each key maps to the corresponding database column name (key names are case-sensitive). It will be passed to either DatabaseObjectEditor::create() or DatabaseObjectEditor::update() . Example: <? php return [ 'environment' => $data [ 'elements' ][ 'environment' . md ], 'eventName' => $data [ 'elements' ][ 'eventname' . md ], 'name' => $data [ 'attributes' ][ 'name' . md ] ];","title":"prepareImport(array $data)"},{"location":"php/api/package_installation_plugins/#validateimportarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method has no return value, instead you should throw an exception if the passed data is invalid.","title":"validateImport(array $data)"},{"location":"php/api/package_installation_plugins/#findexistingitemarray-data","text":"The passed array $data equals the data returned by prepareImport() . This method is expected to return an array with two keys: sql contains the SQL query with placeholders. parameters contains an array with values used for the SQL query.","title":"findExistingItem(array $data)"},{"location":"php/api/package_installation_plugins/#253-example","text":"<? php $sql = \"SELECT * FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND name = ? AND templateName = ? AND eventName = ? AND environment = ?\" ; $parameters = [ $this -> installation -> getPackageID (), $data [ 'name' ], $data [ 'templateName' ], $data [ 'eventName' ], $data [ 'environment' ] ]; return [ 'sql' => $sql , 'parameters' => $parameters ];","title":"2.5.3. Example"},{"location":"php/api/package_installation_plugins/#handledeletearray-items","text":"The passed array $items contains the original node data, similar to prepareImport() . You should make use of this data to remove the matching element from database. Example: <? php $sql = \"DELETE FROM wcf\" . WCF_N . \"_\" . $this -> tableName . \" WHERE packageID = ? AND environment = ? AND eventName = ? AND name = ? AND templateName = ?\" ; $statement = WCF :: getDB () -> prepareStatement ( $sql ); foreach ( $items as $item ) { $statement -> execute ([ $this -> installation -> getPackageID (), $item [ 'elements' ][ 'environment' . md ], $item [ 'elements' ][ 'eventname' . md ], $item [ 'attributes' ][ 'name' . md ], $item [ 'elements' ][ 'templatename' . md ] ]); }","title":"handleDelete(array $items)"},{"location":"php/api/package_installation_plugins/#postimport","text":"Allows you to (optionally) run additionally actions after all elements were processed.","title":"postImport()"},{"location":"php/api/package_installation_plugins/#abstractoptionpackageinstallationplugin","text":"AbstractOptionPackageInstallationPlugin is an abstract implementation for options, used for: ACL Options Options User Options User Group Options","title":"AbstractOptionPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#differences-to-abstractxmlpackageinstallationplugin","text":"","title":"Differences to AbstractXMLPackageInstallationPlugin"},{"location":"php/api/package_installation_plugins/#reservedtags","text":"$reservedTags is a list of reserved tag names so that any tag encountered but not listed here will be added to the database column additionalData . This allows options to store arbitrary data which can be accessed but were not initially part of the PIP specifications.","title":"$reservedTags"},{"location":"php/api/sitemaps/","text":"Sitemaps # This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/sitemaps/#sitemaps","text":"This feature is available with WoltLab Suite 3.1 or newer only. Since version 3.1, WoltLab Suite Core is capable of automatically creating a sitemap. This sitemap contains all static pages registered via the page package installation plugin and which may be indexed by search engines (checking the allowSpidersToIndex parameter and page permissions) and do not expect an object ID. Other pages have to be added to the sitemap as a separate object. The only prerequisite for sitemap objects is that the objects are instances of wcf\\data\\DatabaseObject and that there is a wcf\\data\\DatabaseObjectList implementation. First, we implement the PHP class, which provides us all database objects and optionally checks the permissions for a single object. The class must implement the interface wcf\\system\\sitemap\\object\\ISitemapObjectObjectType . However, in order to have some methods already implemented and ensure backwards compatibility, you should use the abstract class wcf\\system\\sitemap\\object\\AbstractSitemapObjectObjectType . The abstract class takes care of generating the DatabaseObjectList class name and list directly and implements optional methods with the default values. The only method that you have to implement yourself is the getObjectClass() method which returns the fully qualified name of the DatabaseObject class. The DatabaseObject class must implement the interface wcf\\data\\ILinkableObject . Other optional methods are: The getLastModifiedColumn() method returns the name of the column in the database where the last modification date is stored. If there is none, this method must return null . The canView() method checks whether the passed DatabaseObject is visible to the current user with the current user always being a guest. The getObjectListClass() method returns a non-standard DatabaseObjectList class name. The getObjectList() method returns the DatabaseObjectList instance. You can, for example, specify additional query conditions in the method. As an example, the implementation for users looks like this: <? php namespace wcf\\system\\sitemap\\object ; use wcf\\data\\user\\User ; use wcf\\data\\DatabaseObject ; use wcf\\system\\WCF ; /** * User sitemap implementation. * * @author Joshua Ruesweg * @copyright 2001-2017 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Sitemap\\Object * @since 3.1 */ class UserSitemapObject extends AbstractSitemapObjectObjectType { /** * @inheritDoc */ public function getObjectClass () { return User :: class ; } /** * @inheritDoc */ public function getLastModifiedColumn () { return 'lastActivityTime' ; } /** * @inheritDoc */ public function canView ( DatabaseObject $object ) { return WCF :: getSession () -> getPermission ( 'user.profile.canViewUserProfile' ); } } Next, the sitemap object must be registered as an object type: <type> <name> com.example.plugin.sitemap.object.user </name> <definitionname> com.woltlab.wcf.sitemap.object </definitionname> <classname> wcf\\system\\sitemap\\object\\UserSitemapObject </classname> <priority> 0.5 </priority> <changeFreq> monthly </changeFreq> <rebuildTime> 259200 </rebuildTime> </type> In addition to the fully qualified class name, the object type definition com.woltlab.wcf.sitemap.object and the object type name, the parameters priority , changeFreq and rebuildTime must also be specified. priority ( https://www.sitemaps.org/protocol.html#prioritydef ) and changeFreq ( https://www.sitemaps.org/protocol.html#changefreqdef ) are specifications in the sitemaps protocol and can be changed by the user in the ACP. The priority should be 0.5 by default, unless there is an important reason to change it. The parameter rebuildTime specifies the number of seconds after which the sitemap should be regenerated. Finally, you have to create the language variable for the sitemap object. The language variable follows the pattern wcf.acp.sitemap.objectType.{objectTypeName} and is in the category wcf.acp.sitemap .","title":"Sitemaps"},{"location":"php/api/user_activity_points/","text":"User Activity Points # Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_activity_points/#user-activity-points","text":"Users get activity points whenever they create content to award them for their contribution. Activity points are used to determine the rank of a user and can also be used for user conditions, for example for automatic user group assignments. To integrate activity points into your package, you have to register an object type for the defintion com.woltlab.wcf.user.activityPointEvent and specify a default number of points: <type> <name> com.example.foo.activityPointEvent.bar </name> <definitionname> com.woltlab.wcf.user.activityPointEvent </definitionname> <points> 10 </points> </type> The number of points awarded for this type of activity point event can be changed by the administrator in the admin control panel. For this form and the user activity point list shown in the frontend, you have to provide the language item wcf.user.activityPoint.objectType.com.example.foo.activityPointEvent.bar that contains the name of the content for which the activity points are awarded. If a relevant object is created, you have to use UserActivityPointHandler::fireEvent() which expects the name of the activity point event object type, the id of the object for which the points are awarded (though the object id is not used at the moment) and the user who gets the points: UserActivityPointHandler :: getInstance () -> fireEvent ( 'com.example.foo.activityPointEvent.bar' , $bar -> barID , $bar -> userID ); To remove activity points once objects are deleted, you have to use UserActivityPointHandler::removeEvents() which also expects the name of the activity point event object type and additionally an array mapping the id of the user whose activity points will be reduced to the number of objects that are removed for the relevant user: UserActivityPointHandler :: getInstance () -> removeEvents ( 'com.example.foo.activityPointEvent.bar' , [ 1 => 1 , // remove points for one object for user with id `1` 4 => 2 // remove points for two objects for user with id `4` ] );","title":"User Activity Points"},{"location":"php/api/user_notifications/","text":"User Notifications # WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis. objectType.xml # For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id. userNotificationEvent.xml # Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ). Firing Events # When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs ); Marking Notifications as Confirmed # In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"User Notifications"},{"location":"php/api/user_notifications/#user-notifications","text":"WoltLab Suite includes a powerful user notification system that supports notifications directly shown on the website and emails sent immediately or on a daily basis.","title":"User Notifications"},{"location":"php/api/user_notifications/#objecttypexml","text":"For any type of object related to events, you have to define an object type for the object type definition com.woltlab.wcf.notification.objectType : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/objectType.xsd\" > <import> <type> <name> com.woltlab.example.foo </name> <definitionname> com.woltlab.wcf.notification.objectType </definitionname> <classname> example\\system\\user\\notification\\object\\type\\FooUserNotificationObjectType </classname> <category> com.woltlab.example </category> </type> </import> </data> The referenced class FooUserNotificationObjectType has to implement the IUserNotificationObjectType interface, which should be done by extending AbstractUserNotificationObjectType . <? php namespace example\\system\\user\\notification\\object\\type ; use example\\data\\foo\\Foo ; use example\\data\\foo\\FooList ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\user\\notification\\object\\type\\AbstractUserNotificationObjectType ; /** * Represents a foo as a notification object type. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object\\Type */ class FooUserNotificationObjectType extends AbstractUserNotificationObjectType { /** * @inheritDoc */ protected static $decoratorClassName = FooUserNotificationObject :: class ; /** * @inheritDoc */ protected static $objectClassName = Foo :: class ; /** * @inheritDoc */ protected static $objectListClassName = FooList :: class ; } You have to set the class names of the database object ( $objectClassName ) and the related list ( $objectListClassName ). Additionally, you have to create a class that implements the IUserNotificationObject whose name you have to set as the value of the $decoratorClassName property. <? php namespace example\\system\\user\\notification\\object ; use example\\data\\foo\\Foo ; use wcf\\data\\DatabaseObjectDecorator ; use wcf\\system\\user\\notification\\object\\IUserNotificationObject ; /** * Represents a foo as a notification object. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Object * * @method Foo getDecoratedObject() * @mixin Foo */ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { /** * @inheritDoc */ protected static $baseClass = Foo :: class ; /** * @inheritDoc */ public function getTitle () { return $this -> getDecoratedObject () -> getTitle (); } /** * @inheritDoc */ public function getURL () { return $this -> getDecoratedObject () -> getLink (); } /** * @inheritDoc */ public function getAuthorID () { return $this -> getDecoratedObject () -> userID ; } } The getTitle() method returns the title of the object. In this case, we assume that the Foo class has implemented the ITitledObject interface so that the decorated Foo can handle this method call itself. The getURL() method returns the link to the object. As for the getTitle() , we assume that the Foo class has implemented the ILinkableObject interface so that the decorated Foo can also handle this method call itself. The getAuthorID() method returns the id of the user who created the decorated Foo object. We assume that Foo objects have a userID property that contains this id.","title":"objectType.xml"},{"location":"php/api/user_notifications/#usernotificationeventxml","text":"Each event that you fire in your package needs to be registered using the user notification event package installation plugin . An example file might look like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/2019/userNotificationEvent.xsd\" > <import> <event> <name> bar </name> <objecttype> com.woltlab.example.foo </objecttype> <classname> example\\system\\user\\notification\\event\\FooUserNotificationEvent </classname> <preset> 1 </preset> </event> </import> </data> Here, you reference the user notification object type created via objectType.xml . The referenced class in the <classname> element has to implement the IUserNotificationEvent interface by extending the AbstractUserNotificationEvent class or the AbstractSharedUserNotificationEvent class if you want to pre-load additional data before processing notifications. In AbstractSharedUserNotificationEvent::prepare() , you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed. <? php namespace example\\system\\user\\notification\\event ; use example\\system\\cache\\runtime\\BazRuntimeCache ; use example\\system\\user\\notification\\object\\FooUserNotificationObject ; use wcf\\system\\email\\Email ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\user\\notification\\event\\AbstractSharedUserNotificationEvent ; /** * Notification event for foos. * * @author Matthias Schmidt * @copyright 2001-2017 WoltLab GmbH * @license WoltLab License <http://www.woltlab.com/license-agreement.html> * @package WoltLabSuite\\Example\\System\\User\\Notification\\Event * * @method FooUserNotificationObject getUserNotificationObject() */ class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent { /** * @inheritDoc */ protected $stackable = true ; /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function checkAccess () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); if ( ! $this -> getUserNotificationObject () -> isAccessible ()) { // do some cleanup, if necessary return false ; } return true ; } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEmailMessage ( $notificationType = 'instant' ) { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); $messageID = '<com.woltlab.example.baz/' . $this -> getUserNotificationObject () -> bazID . '@' . Email :: getHost () . '>' ; return [ 'application' => 'example' , 'in-reply-to' => [ $messageID ], 'message-id' => 'com.woltlab.example.foo/' . $this -> getUserNotificationObject () -> fooID , 'references' => [ $messageID ], 'template' => 'email_notification_foo' ]; } /** * @inheritDoc * @since 5.0 */ public function getEmailTitle () { $this -> getUserNotificationObject () -> setBaz ( BazRuntimeCache :: getInstance () -> getObject ( $this -> getUserNotificationObject () -> bazID )); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.mail.title' , [ 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** @noinspection PhpMissingParentCallCommonInspection */ /** * @inheritDoc */ public function getEventHash () { return sha1 ( $this -> eventID . '-' . $this -> getUserNotificationObject () -> bazID ); } /** * @inheritDoc */ public function getLink () { return LinkHandler :: getInstance () -> getLink ( 'Foo' , [ 'application' => 'example' , 'object' => $this -> getUserNotificationObject () -> getDecoratedObject () ]); } /** * @inheritDoc */ public function getMessage () { $authors = $this -> getAuthors (); $count = count ( $authors ); if ( $count > 1 ) { if ( isset ( $authors [ 0 ])) { unset ( $authors [ 0 ]); } $count = count ( $authors ); return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message.stacked' , [ 'author' => $this -> author , 'authors' => array_values ( $authors ), 'count' => $count , 'guestTimesTriggered' => $this -> notification -> guestTimesTriggered , 'message' => $this -> getUserNotificationObject (), 'others' => $count - 1 ]); } return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.message' , [ 'author' => $this -> author , 'userNotificationObject' => $this -> getUserNotificationObject () ]); } /** * @inheritDoc */ public function getTitle () { $count = count ( $this -> getAuthors ()); if ( $count > 1 ) { return $this -> getLanguage () -> getDynamicVariable ( 'example.foo.notification.title.stacked' , [ 'count' => $count , 'timesTriggered' => $this -> notification -> timesTriggered ]); } return $this -> getLanguage () -> get ( 'example.foo.notification.title' ); } /** * @inheritDoc */ protected function prepare () { BazRuntimeCache :: getInstance () -> cacheObjectID ( $this -> getUserNotificationObject () -> bazID ); } } The $stackable property is false by default and has to be explicitly set to true if stacking of notifications should be enabled. Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users. For example, if something is liked by one user and then liked again by another user before the recipient of the notification has confirmed it, the existing notification will be amended to include both users who liked the content. Stacking can thus be used to avoid cluttering the notification list of users. The checkAccess() method makes sure that the active user still has access to the object related to the notification. If that is not the case, the user notification system will automatically deleted the user notification based on the return value of the method. If you have any cached values related to notifications, you should also reset these values here. The getEmailMessage() method return data to create the instant email or the daily summary email. For instant emails ( $notificationType = 'instant' ), you have to return an array like the one shown in the code above with the following components: application : abbreviation of application in-reply-to (optional): message id of the notification for the parent item and used to improve the ordering in threaded email clients message-id (optional): message id of the notification mail and has to be used in in-reply-to and references for follow up mails references (optional): all of the message ids of parent items (i.e. recursive in-reply-to) template : name of the template used to render the email body, should start with email_ variables (optional): template variables passed to the email template where they can be accessed via $notificationContent[variables] For daily emails ( $notificationType = 'daily' ), only application , template , and variables are supported. - The getEmailTitle() returns the title of the instant email sent to the user. By default, getEmailTitle() simply calls getTitle() . - The getEventHash() method returns a hash by which user notifications are grouped. Here, we want to group them not by the actual Foo object but by its parent Baz object and thus overwrite the default implementation provided by AbstractUserNotificationEvent . - The getLink() returns the link to the Foo object the notification belongs to. - The getMessage() method and the getTitle() return the message and the title of the user notification respectively. By checking the value of count($this->getAuthors()) , we check if the notification is stacked, thus if the event has been triggered for multiple users so that different languages items are used. If your notification event does not support stacking, this distinction is not necessary. - The prepare() method is called for each user notification before all user notifications are rendered. This allows to tell runtime caches to prepare to load objects later on (see Runtime Caches ).","title":"userNotificationEvent.xml"},{"location":"php/api/user_notifications/#firing-events","text":"When the action related to a user notification is executed, you can use UserNotificationHandler::fireEvent() to create the notifications: $recipientIDs = []; // fill with user ids of the recipients of the notification UserNotificationHandler :: getInstance () -> fireEvent ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name new FooUserNotificationObject ( new Foo ( $fooID )), // object related to the event $recipientIDs );","title":"Firing Events"},{"location":"php/api/user_notifications/#marking-notifications-as-confirmed","text":"In some instances, you might want to manually mark user notifications as confirmed without the user manually confirming them, for example when they visit the page that is related to the user notification. In this case, you can use UserNotificationHandler::markAsConfirmed() : $recipientIDs = []; // fill with user ids of the recipients of the notification $fooIDs = []; // fill with ids of related foo objects UserNotificationHandler :: getInstance () -> markAsConfirmed ( 'bar' , // event name 'com.woltlab.example.foo' , // event object type name $recipientIDs , $fooIDs );","title":"Marking Notifications as Confirmed"},{"location":"php/api/form_builder/dependencies/","text":"Form Node Dependencies # Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript. IFormFieldDependency # The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects. Default Dependencies # WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values. JavaScript Implementation # To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract . Examples # If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Dependencies"},{"location":"php/api/form_builder/dependencies/#form-node-dependencies","text":"Form node dependencies allow to make parts of a form dynamically available or unavailable depending on the values of form fields. Dependencies are always added to the object whose visibility is determined by certain form fields. They are not added to the form field\u2019s whose values determine the visibility! An example is a text form field that should only be available if a certain option from a single selection form field is selected. Form builder\u2019s dependency system supports such scenarios and also automatically making form containers unavailable once all of its children are unavailable. If a form node has multiple dependencies and one of them is not met, the form node is unavailable. A form node not being available due to dependencies has to the following consequences: The form field value is not validated. It is, however, read from the request data as all request data needs to be read first so that the dependencies can determine whether they are met or not. No data is collected for the form field and returned by IFormDocument::getData() . In the actual form, the form field will be hidden via JavaScript.","title":"Form Node Dependencies"},{"location":"php/api/form_builder/dependencies/#iformfielddependency","text":"The basis of the dependencies is the IFormFieldDependency interface that has to be implemented by every dependency class. The interface requires the following methods: checkDependency() checks if the dependency is met, thus if the dependant form field should be considered available. dependentNode(IFormNode $node) and getDependentNode() can be used to set and get the node whose availability depends on the referenced form field. TFormNode::addDependency() automatically calls dependentNode(IFormNode $node) with itself as the dependent node, thus the dependent node is automatically set by the API. field(IFormField $field) and getField() can be used to set and get the form field that influences the availability of the dependent node. fieldId($fieldId) and getFieldId() can be used to set and get the id of the form field that influences the availability of the dependent node. getHtml() returns JavaScript code required to ensure the dependency in the form output. getId() returns the id of the dependency used to identify multiple dependencies of the same form node. static create($id) is the factory method that has to be used to create new dependencies with the given id. AbstractFormFieldDependency provides default implementations for all methods except for checkDependency() . Using fieldId($fieldId) instead of field(IFormField $field) makes sense when adding the dependency directly when setting up the form: $container -> appendChildren ([ FooField :: create ( 'a' ), BarField :: create ( 'b' ) -> addDependency ( BazDependency :: create ( 'a' ) -> fieldId ( 'a' ) ) ]); Here, without an additional assignment, the first field with id a cannot be accessed thus fieldId($fieldId) should be used as the id of the relevant field is known. When the form is built, all dependencies that only know the id of the relevant field and do not have a reference for the actual object are populated with the actual form field objects.","title":"IFormFieldDependency"},{"location":"php/api/form_builder/dependencies/#default-dependencies","text":"WoltLab Suite Core delivers the following two default dependency classes by default: NonEmptyFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is not empty (being empty is determined using PHP\u2019s empty function). ValueFormFieldDependency can be used to ensure that a node is only shown if the value of the referenced form field is from a specified list of of values (see methods values($values) and getValues() ). Additionally, via negate($negate = true) and isNegated() , the locic can also be inverted by requiring the value of the referenced form field not to be from a specified list of values.","title":"Default Dependencies"},{"location":"php/api/form_builder/dependencies/#javascript-implementation","text":"To ensure that dependent node are correctly shown and hidden when changing the value of referenced form fields, every PHP dependency class has a corresponding JavaScript module that checks the dependency in the browser. Every JavaScript dependency has to extend WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract and implement the checkDependency() function, the JavaScript version of IFormFieldDependency::checkDependency() . All of the JavaScript dependency objects automatically register themselves during initialization with the WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager which takes care of checking the dependencies at the correct points in time. Additionally, the dependency manager also ensures that form containers in which all children are hidden due to dependencies are also hidden and, once any child becomes available again, makes the container also available again. Every form container has to create a matching form container dependency object from a module based on WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract .","title":"JavaScript Implementation"},{"location":"php/api/form_builder/dependencies/#examples","text":"If $booleanFormField is an instance of BooleanFormField and the text form field $textFormField should only be available if \u201cYes\u201d has been selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'booleanFormField' ) -> field ( $booleanFormField ) ); If $singleSelectionFormField is an instance of SingleSelectionFormField that offers the options 1 , 2 , and 3 and $textFormField should only be available if 1 or 3 is selected, the following condition has to be set up: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 1 , 3 ]) ); If, in contrast, $singleSelectionFormField has many available options and 7 is the only option for which $textFormField should not be available, negate() should be used: $textFormField -> addDependency ( NonEmptyFormFieldDependency :: create ( 'singleSelectionFormField' ) -> field ( $singleSelectionFormField ) -> values ([ 7 ]) -> negate () );","title":"Examples"},{"location":"php/api/form_builder/form_fields/","text":"Form Builder Fields # Abstract Form Fields # The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes. AbstractFormField # AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class. AbstractNumericFormField # AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any . General Form Fields # The following form fields are general reusable fields without any underlying context. BooleanFormField # BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField . CheckboxFormField # Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox. ClassNameFormField # ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively). DateFormField # DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled. DescriptionFormField # DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label. EmailFormField # EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField . FloatFormField # FloatFormField is an implementation of AbstractNumericFormField for floating point numbers. IconFormField # IconFormField is a form field to select a FontAwesome icon. IntegerFormField # IntegerFormField is an implementation of AbstractNumericFormField for integers. IsDisabledFormField # IsDisabledFormField is a boolean form field with isDisabled as the default id. ItemListFormField # ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key. MultilineTextFormField # MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered. MultipleSelectionFormField # MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value. RadioButtonFormField # RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField . RatingFormField # RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange . ShowOrderFormField # ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering. SingleSelectionFormField # SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value. SortOrderFormField # SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending . TextFormField # TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField . TitleFormField # TitleFormField is a text form field with title as the default id and wcf.global.title as the default label. UrlFormField # UrlFormField is a text form field whose values are checked via Url::is() . Specific Fields # The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation. AclFormField # AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately. ButtonFormField # Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked. CaptchaFormField # CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method. ContentLanguageFormField # ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField . LabelFormField # LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used. OptionFormField # OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options . SimpleAclFormField # SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. SingleMediaSelectionFormField # SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set. TagFormField # TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key. UploadFormField # UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed. Provide value from database object # To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files. Process files # To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded. UserFormField # UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method. UserPasswordField # Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField UserGroupOptionFormField # UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions . UsernameFormField # UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length. Wysiwyg form container # To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container. WysiwygAttachmentFormField # WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments. WysiwygPollFormContainer # WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built. WysiwygSmileyFormContainer # WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category. WysiwygSmileyFormNode # WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer . Example # The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes () WysiwygFormField # WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key. TWysiwygFormNode # All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id. Single-Use Form Fields # The following form fields are specific for certain forms and hardly reusable in other contexts. BBCodeAttributesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode. DevtoolsProjectExcludedPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project. DevtoolsProjectInstructionsFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project. DevtoolsProjectOptionalPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project. DevtoolsProjectRequiredPackagesFormField # DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"Fields"},{"location":"php/api/form_builder/form_fields/#form-builder-fields","text":"","title":"Form Builder Fields"},{"location":"php/api/form_builder/form_fields/#abstract-form-fields","text":"The following form field classes cannot be instantiated directly because they are abstract, but they can/must be used when creating own form field classes.","title":"Abstract Form Fields"},{"location":"php/api/form_builder/form_fields/#abstractformfield","text":"AbstractFormField is the abstract default implementation of the IFormField interface and it is expected that every implementation of IFormField implements the interface by extending this class.","title":"AbstractFormField"},{"location":"php/api/form_builder/form_fields/#abstractnumericformfield","text":"AbstractNumericFormField is the abstract implementation of a form field handling a single numeric value. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , IInputModeFormField , IMaximumFormField , IMinimumFormField , INullableFormField , IPlaceholderFormField and ISuffixedFormField . If the property $integerValues is true , the form field works with integer values, otherwise it works with floating point numbers. The methods step($step = null) and getStep() can be used to set and get the step attribute of the input element. The default step for form fields with integer values is 1 . Otherwise, the default step is any .","title":"AbstractNumericFormField"},{"location":"php/api/form_builder/form_fields/#general-form-fields","text":"The following form fields are general reusable fields without any underlying context.","title":"General Form Fields"},{"location":"php/api/form_builder/form_fields/#booleanformfield","text":"BooleanFormField is used for boolean ( 0 or 1 , yes or no ) values. Objects of this class require a label. The return value of getSaveValue() is the integer representation of the boolean value, i.e. 0 or 1 . The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , and IImmutableFormField .","title":"BooleanFormField"},{"location":"php/api/form_builder/form_fields/#checkboxformfield","text":"Only available since version 5.3.2. CheckboxFormField extends BooleanFormField and offers a simple HTML checkbox.","title":"CheckboxFormField"},{"location":"php/api/form_builder/form_fields/#classnameformfield","text":"ClassNameFormField is a text form field that supports additional settings, specific to entering a PHP class name: classExists($classExists = true) and getClassExists() can be used to ensure that the entered class currently exists in the installation. By default, the existance of the entered class is required. implementedInterface($interface) and getImplementedInterface() can be used to ensure that the entered class implements the specified interface. By default, no interface is required. parentClass($parentClass) and getParentClass() can be used to ensure that the entered class extends the specified class. By default, no parent class is required. instantiable($instantiable = true) and isInstantiable() can be used to ensure that the entered class is instantiable. By default, entered classes have to instantiable. Additionally, the default id of a ClassNameFormField object is className , the default label is wcf.form.field.className , and if either an interface or a parent class is required, a default description is set if no description has already been set ( wcf.form.field.className.description.interface and wcf.form.field.className.description.parentClass , respectively).","title":"ClassNameFormField"},{"location":"php/api/form_builder/form_fields/#dateformfield","text":"DateFormField is a form field to enter a date (and optionally a time). The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and INullableFormField . The following methods are specific to this form field class: earliestDate($earliestDate) and getEarliestDate() can be used to get and set the earliest selectable/valid date and latestDate($latestDate) and getLatestDate() can be used to get and set the latest selectable/valid date. The date passed to the setters must have the same format as set via saveValueFormat() . If a custom format is used, that format has to be set via saveValueFormat() before calling any of the setters. saveValueFormat($saveValueFormat) and getSaveValueFormat() can be used to specify the date format of the value returned by getSaveValue() . By default, U is used as format. The PHP manual provides an overview of supported formats. supportTime($supportsTime = true) and supportsTime() can be used to toggle whether, in addition to a date, a time can also be specified. By default, specifying a time is disabled.","title":"DateFormField"},{"location":"php/api/form_builder/form_fields/#descriptionformfield","text":"DescriptionFormField is a multi-line text form field with description as the default id and wcf.global.description as the default label.","title":"DescriptionFormField"},{"location":"php/api/form_builder/form_fields/#emailformfield","text":"EmailFormField is a form field to enter an email address which is internally validated using UserUtil::isValidEmail() . The class implements IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , ICssClassFormField , II18nFormField , IImmutableFormField , IInputModeFormField , IPatternFormField , and IPlaceholderFormField .","title":"EmailFormField"},{"location":"php/api/form_builder/form_fields/#floatformfield","text":"FloatFormField is an implementation of AbstractNumericFormField for floating point numbers.","title":"FloatFormField"},{"location":"php/api/form_builder/form_fields/#iconformfield","text":"IconFormField is a form field to select a FontAwesome icon.","title":"IconFormField"},{"location":"php/api/form_builder/form_fields/#integerformfield","text":"IntegerFormField is an implementation of AbstractNumericFormField for integers.","title":"IntegerFormField"},{"location":"php/api/form_builder/form_fields/#isdisabledformfield","text":"IsDisabledFormField is a boolean form field with isDisabled as the default id.","title":"IsDisabledFormField"},{"location":"php/api/form_builder/form_fields/#itemlistformfield","text":"ItemListFormField is a form field in which multiple values can be entered and returned in different formats as save value. The class implements IAttributeFormField , IAutoFocusFormField , ICssClassFormField , IImmutableFormField , and IMultipleFormField . The saveValueType($saveValueType) and getSaveValueType() methods are specific to this form field class and determine the format of the save value. The following save value types are supported: ItemListFormField::SAVE_VALUE_TYPE_ARRAY adds a custom data processor that writes the form field data directly in the parameters array and not in the data sub-array of the parameters array. ItemListFormField::SAVE_VALUE_TYPE_CSV lets the value be returned as a string in which the values are concatenated by commas. ItemListFormField::SAVE_VALUE_TYPE_NSV lets the value be returned as a string in which the values are concatenated by \\n . ItemListFormField::SAVE_VALUE_TYPE_SSV lets the value be returned as a string in which the values are concatenated by spaces. By default, ItemListFormField::SAVE_VALUE_TYPE_CSV is used. If ItemListFormField::SAVE_VALUE_TYPE_ARRAY is used as save value type, ItemListFormField objects register a custom form field data processor to add the relevant array into the $parameters array directly using the object property as the array key.","title":"ItemListFormField"},{"location":"php/api/form_builder/form_fields/#multilinetextformfield","text":"MultilineTextFormField is a text form field that supports multiple rows of text. The methods rows($rows) and getRows() can be used to set and get the number of rows of the textarea elements. The default number of rows is 10 . These methods do not , however, restrict the number of text rows that canbe entered.","title":"MultilineTextFormField"},{"location":"php/api/form_builder/form_fields/#multipleselectionformfield","text":"MultipleSelectionFormField is a form fields that allows the selection of multiple options out of a predefined list of available options. The class implements IAttributeFormField , ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and no option is selected, null is returned as the save value.","title":"MultipleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#radiobuttonformfield","text":"RadioButtonFormField is a form fields that allows the selection of a single option out of a predefined list of available options using radiobuttons. The class implements IAttributeFormField , ICssClassFormField , IImmutableFormField , and ISelectionFormField .","title":"RadioButtonFormField"},{"location":"php/api/form_builder/form_fields/#ratingformfield","text":"RatingFormField is a form field to set a rating for an object. The class implements IImmutableFormField , IMaximumFormField , IMinimumFormField , and INullableFormField . Form fields of this class have rating as their default id, wcf.form.field.rating as their default label, 1 as their default minimum, and 5 as their default maximum. For this field, the minimum and maximum refer to the minimum and maximum rating an object can get. When the field is shown, there will be maximum() - minimum() + 1 icons be shown with additional CSS classes that can be set and gotten via defaultCssClasses(array $cssClasses) and getDefaultCssClasses() . If a rating values is set, the first getValue() icons will instead use the classes that can be set and gotten via activeCssClasses(array $cssClasses) and getActiveCssClasses() . By default, the only default class is fa-star-o and the active classes are fa-star and orange .","title":"RatingFormField"},{"location":"php/api/form_builder/form_fields/#showorderformfield","text":"ShowOrderFormField is a single selection form field for which the selected value determines the position at which an object is shown. The show order field provides a list of all siblings and the object will be positioned after the selected sibling. To insert objects at the very beginning, the options() automatically method prepends an additional option for that case so that only the existing siblings need to be passed. The default id of instances of this class is showOrder and their default label is wcf.form.field.showOrder . It is important that the relevant object property is always kept updated. Whenever a new object is added or an existing object is edited or delete, the values of the other objects have to be adjusted to ensure consecutive numbering.","title":"ShowOrderFormField"},{"location":"php/api/form_builder/form_fields/#singleselectionformfield","text":"SingleSelectionFormField is a form fields that allows the selection of a single option out of a predefined list of available options. The class implements ICssClassFormField , IFilterableSelectionFormField , IImmutableFormField , and INullableFormField . If the field is nullable and the current form field value is considered empty by PHP, null is returned as the save value.","title":"SingleSelectionFormField"},{"location":"php/api/form_builder/form_fields/#sortorderformfield","text":"SingleSelectionFormField is a single selection form field with default id sortOrder , default label wcf.global.showOrder and default options ASC: wcf.global.sortOrder.ascending and DESC: wcf.global.sortOrder.descending .","title":"SortOrderFormField"},{"location":"php/api/form_builder/form_fields/#textformfield","text":"TextFormField is a form field that allows entering a single line of text. The class implements IAttributeFormField , IAutoCompleteFormField , ICssClassFormField , IImmutableFormField , II18nFormField , IInputModeFormField , IMaximumLengthFormField , IMinimumLengthFormField , IPatternFormField , and IPlaceholderFormField .","title":"TextFormField"},{"location":"php/api/form_builder/form_fields/#titleformfield","text":"TitleFormField is a text form field with title as the default id and wcf.global.title as the default label.","title":"TitleFormField"},{"location":"php/api/form_builder/form_fields/#urlformfield","text":"UrlFormField is a text form field whose values are checked via Url::is() .","title":"UrlFormField"},{"location":"php/api/form_builder/form_fields/#specific-fields","text":"The following form fields are reusable fields that generally are bound to a certain API or DatabaseObject implementation.","title":"Specific Fields"},{"location":"php/api/form_builder/form_fields/#aclformfield","text":"AclFormField is used for setting up acl values for specific objects. The class implements IObjectTypeFormField and requires an object type of the object type definition com.woltlab.wcf.acl . Additionally, the class provides the methods categoryName($categoryName) and getCategoryName() that allow setting a specific name or filter for the acl option categories whose acl options are shown. A category name of null signals that no category filter is used. AclFormField objects register a custom form field data processor to add the relevant ACL object type id into the $parameters array directly using {$objectProperty}_aclObjectTypeID as the array key. The relevant database object action method is expected, based on the given ACL object type id, to save the ACL option values appropriately.","title":"AclFormField"},{"location":"php/api/form_builder/form_fields/#buttonformfield","text":"Only available since version 5.4. ButtonFormField shows a submit button as part of the form. The class implements IAttributeFormField and ICssClassFormField . Specifically for this form field, there is the IsNotClickedFormFieldDependency dependency with which certain parts of the form will only be processed if the relevent button has not clicked.","title":"ButtonFormField"},{"location":"php/api/form_builder/form_fields/#captchaformfield","text":"CaptchaFormField is used to add captcha protection to the form. You must specify a captcha object type ( com.woltlab.wcf.captcha ) using the objectType() method.","title":"CaptchaFormField"},{"location":"php/api/form_builder/form_fields/#contentlanguageformfield","text":"ContentLanguageFormField is used to select the content language of an object. Fields of this class are only available if multilingualism is enabled and if there are content languages. The class implements IImmutableFormField .","title":"ContentLanguageFormField"},{"location":"php/api/form_builder/form_fields/#labelformfield","text":"LabelFormField is used to select a label from a specific label group. The class implements IObjectTypeFormNode . The labelGroup(ViewableLabelGroup $labelGroup) and getLabelGroup() methods are specific to this form field class and can be used to set and get the label group whose labels can be selected. Additionally, there is the static method createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs) that can be used to create all relevant label form fields for a given list of label groups. In most cases, LabelFormField::createFields() should be used.","title":"LabelFormField"},{"location":"php/api/form_builder/form_fields/#optionformfield","text":"OptionFormField is an item list form field to set a list of options. The class implements IPackagesFormField and only options of the set packages are considered available. The default label of instances of this class is wcf.form.field.option and their default id is options .","title":"OptionFormField"},{"location":"php/api/form_builder/form_fields/#simpleaclformfield","text":"SimpleAclFormField is used for setting up simple acl values (one yes / no option per user and user group) for specific objects. SimpleAclFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"SimpleAclFormField"},{"location":"php/api/form_builder/form_fields/#singlemediaselectionformfield","text":"SingleMediaSelectionFormField is used to select a specific media file. The class implements IImmutableFormField . The following methods are specific to this form field class: imageOnly($imageOnly = true) and isImageOnly() can be used to set and check if only images may be selected. getMedia() returns the media file based on the current field value if a field is set.","title":"SingleMediaSelectionFormField"},{"location":"php/api/form_builder/form_fields/#tagformfield","text":"TagFormField is a form field to enter tags. The class implements IAttributeFormField and IObjectTypeFormNode . Arrays passed to TagFormField::values() can contain tag names as strings and Tag objects. The default label of instances of this class is wcf.tagging.tags and their default description is wcf.tagging.tags.description . TagFormField objects register a custom form field data processor to add the array with entered tag names into the $parameters array directly using the object property as the array key.","title":"TagFormField"},{"location":"php/api/form_builder/form_fields/#uploadformfield","text":"UploadFormField is a form field that allows uploading files by the user. UploadFormField objects register a custom form field data processor to add the array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property as the array key. Also it registers the removed files as an array of wcf\\system\\file\\upload\\UploadFile\\UploadFile into the $parameters array directly using the object property with the suffix _removedFiles as the array key. The field supports additional settings: - imageOnly($imageOnly = true) and isImageOnly() can be used to ensure that the uploaded files are only images. - allowSvgImage($allowSvgImages = true) and svgImageAllowed() can be used to allow SVG images, if the image only mode is enabled (otherwise, the method will throw an exception). By default, SVG images are not allowed.","title":"UploadFormField"},{"location":"php/api/form_builder/form_fields/#provide-value-from-database-object","text":"To provide values from a database object, you should implement the method get{$objectProperty}UploadFileLocations() to your database object class. This method must return an array of strings with the locations of the files.","title":"Provide value from database object"},{"location":"php/api/form_builder/form_fields/#process-files","text":"To process files in the database object action class, you must rename the file to the final destination. You get the temporary location, by calling the method getLocation() on the given UploadFile objects. After that, you call setProcessed($location) with $location contains the new file location. This method sets the isProcessed flag to true and saves the new location. For updating files, it is relevant, whether a given file is already processed or not. For this case, the UploadFile object has an method isProcessed() which indicates, whether a file is already processed or new uploaded.","title":"Process files"},{"location":"php/api/form_builder/form_fields/#userformfield","text":"UserFormField is a form field to enter existing users. The class implements IAutoCompleteFormField , IAutoFocusFormField , IImmutableFormField , IMultipleFormField , and INullableFormField . While the user is presented the names of the specified users in the user interface, the field returns the ids of the users as data. The relevant UserProfile objects can be accessed via the getUsers() method.","title":"UserFormField"},{"location":"php/api/form_builder/form_fields/#userpasswordfield","text":"Only available since version 5.4. UserPasswordField is a form field for users' to enter their current password. The class implements IAttributeFormField , IAttributeFormField , IAutoCompleteFormField , IAutoFocusFormField , and IPlaceholderFormField","title":"UserPasswordField"},{"location":"php/api/form_builder/form_fields/#usergroupoptionformfield","text":"UserGroupOptionFormField is an item list form field to set a list of user group options/permissions. The class implements IPackagesFormField and only user group options of the set packages are considered available. The default label of instances of this class is wcf.form.field.userGroupOption and their default id is permissions .","title":"UserGroupOptionFormField"},{"location":"php/api/form_builder/form_fields/#usernameformfield","text":"UsernameFormField is used for entering one non-existing username. The class implements IAttributeFormField , IImmutableFormField , IMaximumLengthFormField , IMinimumLengthFormField , INullableFormField , and IPlaceholderFormField . As usernames have a system-wide restriction of a minimum length of 3 and a maximum length of 100 characters, these values are also used as the default value for the field\u2019s minimum and maximum length.","title":"UsernameFormField"},{"location":"php/api/form_builder/form_fields/#wysiwyg-form-container","text":"To integrate a wysiwyg editor into a form, you have to create a WysiwygFormContainer object. This container takes care of creating all necessary form nodes listed below for a wysiwyg editor. When creating the container object, its id has to be the id of the form field that will manage the actual text. The following methods are specific to this form container class: addSettingsNode(IFormChildNode $settingsNode) and addSettingsNodes(array $settingsNodes) can be used to add nodes to the settings tab container. attachmentData($objectType, $parentObjectID) can be used to set the data relevant for attachment support. By default, not attachment data is set, thus attachments are not supported. getAttachmentField() , getPollContainer() , getSettingsContainer() , getSmiliesContainer() , and getWysiwygField() can be used to get the different components of the wysiwyg form container once the form has been built. enablePreviewButton($enablePreviewButton) can be used to set whether the preview button for the message is shown or not. By default, the preview button is shown. This method is only relevant before the form is built. Afterwards, the preview button availability can not be changed. Only available since WoltLab Suite Core 5.3. getObjectId() returns the id of the edited object or 0 if no object is edited. getPreselect() , preselect($preselect) can be used to set the value of the wysiwyg tab menu's data-preselect attribute used to determine which tab is preselected. By default, the preselect is 'true' which is used to pre-select the first tab. messageObjectType($messageObjectType) can be used to set the message object type. pollObjectType($pollObjectType) can be used to set the poll object type. By default, no poll object type is set, thus the poll form field container is not available. supportMentions($supportMentions) can be used to set if mentions are supported. By default, mentions are not supported. This method is only relevant before the form is built. Afterwards, mention support can only be changed via the wysiwyg form field. supportSmilies($supportSmilies) can be used to set if smilies are supported. By default, smilies are supported. This method is only relevant before the form is built. Afterwards, smiley availability can only be changed via changing the availability of the smilies form container.","title":"Wysiwyg form container"},{"location":"php/api/form_builder/form_fields/#wysiwygattachmentformfield","text":"WysiwygAttachmentFormField provides attachment support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . The methods attachmentHandler(AttachmentHandler $attachmentHandler) and getAttachmentHandler() can be used to set and get the AttachmentHandler object that is used for uploaded attachments.","title":"WysiwygAttachmentFormField"},{"location":"php/api/form_builder/form_fields/#wysiwygpollformcontainer","text":"WysiwygPollFormContainer provides poll support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygPollFormContainer contains all form fields that are required to create polls and requires edited objects to implement IPollContainer . The following methods are specific to this form container class: getEndTimeField() returns the form field to set the end time of the poll once the form has been built. getIsChangeableField() returns the form field to set if poll votes can be changed once the form has been built. getIsPublicField() returns the form field to set if poll results are public once the form has been built. getMaxVotesField() returns the form field to set the maximum number of votes once the form has been built. getOptionsField() returns the form field to set the poll options once the form has been built. getQuestionField() returns the form field to set the poll question once the form has been built. getResultsRequireVoteField() returns the form field to set if viewing the poll results requires voting once the form has been built. getSortByVotesField() returns the form field to set if the results are sorted by votes once the form has been built.","title":"WysiwygPollFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformcontainer","text":"WysiwygSmileyFormContainer provides smiley support for a wysiwyg editor via a tab in the menu below the editor. This class should not be used directly but only via WysiwygFormContainer . WysiwygSmileyFormContainer creates a sub-tab for each smiley category.","title":"WysiwygSmileyFormContainer"},{"location":"php/api/form_builder/form_fields/#wysiwygsmileyformnode","text":"WysiwygSmileyFormNode is contains the smilies of a specific category. This class should not be used directly but only via WysiwygSmileyFormContainer .","title":"WysiwygSmileyFormNode"},{"location":"php/api/form_builder/form_fields/#example","text":"The following code creates a WYSIWYG editor component for a message object property. As smilies are supported by default and an attachment object type is given, the tab menu below the editor has two tabs: \u201cSmilies\u201d and \u201cAttachments\u201d. Additionally, mentions and quotes are supported. WysiwygFormContainer :: create ( 'message' ) -> label ( 'foo.bar.message' ) -> messageObjectType ( 'com.example.foo.bar' ) -> attachmentData ( 'com.example.foo.bar' ) -> supportMentions () -> supportQuotes ()","title":"Example"},{"location":"php/api/form_builder/form_fields/#wysiwygformfield","text":"WysiwygFormField is used for wysiwyg editor form fields. This class should, in general, not be used directly but only via WysiwygFormContainer . The class implements IAttributeFormField , IMaximumLengthFormField , IMinimumLengthFormField , and IObjectTypeFormNode and requires an object type of the object type definition com.woltlab.wcf.message . The following methods are specific to this form field class: autosaveId($autosaveId) and getAutosaveId() can be used enable automatically saving the current editor contents in the browser using the given id. An empty string signals that autosaving is disabled. lastEditTime($lastEditTime) and getLastEditTime() can be used to set the last time the contents have been edited and saved so that the JavaScript can determine if the contents stored in the browser are older or newer. 0 signals that no last edit time has been set. supportAttachments($supportAttachments) and supportsAttachments() can be used to set and check if the form field supports attachments. !!! warning \"It is not sufficient to simply signal attachment support via these methods for attachments to work. These methods are relevant internally to signal the Javascript code that the editor supports attachments. Actual attachment support is provided by WysiwygAttachmentFormField .\" - supportMentions($supportMentions) and supportsMentions() can be used to set and check if the form field supports mentions of other users. WysiwygFormField objects register a custom form field data processor to add the relevant simple ACL data array into the $parameters array directly using the object property as the array key.","title":"WysiwygFormField"},{"location":"php/api/form_builder/form_fields/#twysiwygformnode","text":"All form nodes that need to know the id of the WysiwygFormField field should use TWysiwygFormNode . This trait provides getWysiwygId() and wysiwygId($wysiwygId) to get and set the relevant wysiwyg editor id.","title":"TWysiwygFormNode"},{"location":"php/api/form_builder/form_fields/#single-use-form-fields","text":"The following form fields are specific for certain forms and hardly reusable in other contexts.","title":"Single-Use Form Fields"},{"location":"php/api/form_builder/form_fields/#bbcodeattributesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the attributes of a BBCode.","title":"BBCodeAttributesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectexcludedpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the excluded packages of a devtools project.","title":"DevtoolsProjectExcludedPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectinstructionsformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the installation and update instructions of a devtools project.","title":"DevtoolsProjectInstructionsFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectoptionalpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the optional packages of a devtools project.","title":"DevtoolsProjectOptionalPackagesFormField"},{"location":"php/api/form_builder/form_fields/#devtoolsprojectrequiredpackagesformfield","text":"DevtoolsProjectExcludedPackagesFormField is a form field for setting the required packages of a devtools project.","title":"DevtoolsProjectRequiredPackagesFormField"},{"location":"php/api/form_builder/overview/","text":"Form Builder # Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar. Advantages of Form Builder # WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form. Form Builder Components # Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems. AbstractFormBuilderForm # To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object. DialogFormDocument # Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"Overview"},{"location":"php/api/form_builder/overview/#form-builder","text":"Form builder is only available since WoltLab Suite Core 5.2. The migration guide for WoltLab Suite Core 5.2 provides some examples of how to migrate existing forms to form builder that can also help in understanding form builder if the old way of creating forms is familiar.","title":"Form Builder"},{"location":"php/api/form_builder/overview/#advantages-of-form-builder","text":"WoltLab Suite 5.2 introduces a new powerful way of creating forms: form builder. Before taking a closer look at form builder, let us recap how forms are created in previous versions: In general, for each form field, there is a corresponding property of the form's PHP class whose value has to be read from the request data, validated, and passed to the database object action to store the value in a database table. When editing an object, the property's value has to be set using the value of the corresponding property of the edited object. In the form's template, you have to write the <form> element with all of its children: the <section> elements, the <dl> elements, and, of course, the form fields themselves. In summary, this way of creating forms creates much duplicate or at least very similar code and makes it very time consuming if the structure of forms in general or a specific type of form field has to be changed. Form builder, in contrast, relies on PHP objects representing each component of the form, from the form itself down to each form field. This approach makes creating forms as easy as creating some PHP objects, populating them with all the relevant data, and one line of code in the template to print the form.","title":"Advantages of Form Builder"},{"location":"php/api/form_builder/overview/#form-builder-components","text":"Form builder consists of several components that are presented on the following pages: Structure of form builder Form validation and form data Form node dependencies In general, form builder provides default implementation of interfaces by providing either abstract classes or traits. It is expected that the interfaces are always implemented using these abstract classes and traits! This way, if new methods are added to the interfaces, default implementations can be provided by the abstract classes and traits without causing backwards compatibility problems.","title":"Form Builder Components"},{"location":"php/api/form_builder/overview/#abstractformbuilderform","text":"To make using form builder easier, AbstractFormBuilderForm extends AbstractForm and provides most of the code needed to set up a form (of course without specific fields, those have to be added by the concrete form class), like reading and validating form values and using a database object action to use the form data to create or update a database object. In addition to the existing methods inherited by AbstractForm , AbstractFormBuilderForm provides the following methods: buildForm() builds the form in the following steps: Call AbtractFormBuilderForm::createForm() to create the IFormDocument object and add the form fields. Call IFormDocument::build() to build the form. Call AbtractFormBuilderForm::finalizeForm() to finalize the form like adding dependencies. Additionally, between steps 1 and 2 and after step 3, the method provides two events, createForm and buildForm to allow plugins to register event listeners to execute additional code at the right point in time. - createForm() creates the FormDocument object and sets the form mode. Classes extending AbstractFormBuilderForm have to override this method (and call parent::createForm() as the first line in the overridden method) to add concrete form containers and form fields to the bare form document. - finalizeForm() is called after the form has been built and the complete form hierarchy has been established. This method should be overridden to add dependencies, for example. - setFormAction() is called at the end of readData() and sets the form document\u2019s action based on the controller class name and whether an object is currently edited. - If an object is edited, at the beginning of readData() , setFormObjectData() is called which calls IFormDocument::loadValuesFromObject() . If values need to be loaded from additional sources, this method should be used for that. AbstractFormBuilderForm also provides the following (public) properties: $form contains the IFormDocument object created in createForm() . $formAction is either create (default) or edit and handles which method of the database object is called by default ( create is called for $formAction = 'create' and update is called for $formAction = 'edit' ) and is used to set the value of the action template variable. $formObject contains the IStorableObject if the form is used to edit an existing object. For forms used to create objects, $formObject is always null . Edit forms have to manually identify the edited object based on the request data and set the value of $formObject . $objectActionName can be used to set an alternative action to be executed by the database object action that deviates from the default action determined by the value of $formAction . $objectActionClass is the name of the database object action class that is used to create or update the database object.","title":"AbstractFormBuilderForm"},{"location":"php/api/form_builder/overview/#dialogformdocument","text":"Form builder forms can also be used in dialogs. For such forms, DialogFormDocument should be used which provides the additional methods cancelable($cancelable = true) and isCancelable() to set and check if the dialog can be canceled. If a dialog form can be canceled, a cancel button is added. If the dialog form is fetched via an AJAX request, IFormDocument::ajax() has to be called. AJAX forms are registered with WoltLabSuite/Core/Form/Builder/Manager which also supports getting all of the data of a form via the getData(formId) function. The getData() function relies on all form fields creating and registering a WoltLabSuite/Core/Form/Builder/Field/Field object that provides the data of a specific field. To make it as easy as possible to work with AJAX forms in dialogs, WoltLabSuite/Core/Form/Builder/Dialog (abbreviated as FormBuilderDialog from now on) should generally be used instead of WoltLabSuite/Core/Form/Builder/Manager directly. The constructor of FormBuilderDialog expects the following parameters: dialogId : id of the dialog element className : PHP class used to get the form dialog (and save the data if options.submitActionName is set) actionName : name of the action/method of className that returns the dialog; the method is expected to return an array with formId containg the id of the returned form and dialog containing the rendered form dialog options : additional options: actionParameters (default: empty): additional parameters sent during AJAX requests destroyOnClose (default: false ): if true , whenever the dialog is closed, the form is destroyed so that a new form is fetched if the dialog is opened again dialog : additional dialog options used as options during dialog setup onSubmit : callback when the form is submitted (takes precedence over submitActionName ) submitActionName (default: not set): name of the action/method of className called when the form is submitted The three public functions of FormBuilderDialog are: destroy() destroys the dialog, the form, and all of the form fields. getData() returns a Promise that returns the form data. open() opens the dialog. Example: require ([ 'WoltLabSuite/Core/Form/Builder/Dialog' ], function ( FormBuilderDialog ) { var dialog = new FormBuilderDialog ( 'testDialog' , 'wcf\\\\data\\\\test\\\\TestAction' , 'getDialog' , { destroyOnClose : true , dialog : { title : 'Test Dialog' }, submitActionName : 'saveDialog' } ); elById ( 'testDialogButton' ). addEventListener ( 'click' , function () { dialog . open (); }); });","title":"DialogFormDocument"},{"location":"php/api/form_builder/structure/","text":"Structure of Form Builder # Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining. Form Nodes # IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label. IFormNode # IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods. IFormChildNode # IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() . IFormParentNode # IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children. IFormElement # IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element. IObjectTypeFormNode # IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods. CustomFormNode # CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used. TemplateFormNode # TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template. Form Document # A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable. Form Button # A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element. Form Container # A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container. Form Field # A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here . Form Field Interfaces and Traits # WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields: IAttributeFormField # Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used. IAutoCompleteFormField # Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens. IAutoFocusFormField # IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods. ICssClassFormField # Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods. IFileFormField # IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ). IFilterableSelectionFormField # IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods. II18nFormField # II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() . IImmutableFormField # IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable. IInputModeFormField # Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods. IMaximumFormField # IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually. IMaximumLengthFormField # IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() . IMinimumFormField # IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually. IMinimumLengthFormField # IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() . IMultipleFormField # IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually. INullableFormField # INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable. IPackagesFormField # IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods. IPatternFormField # Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods. IPlaceholderFormField # IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods. ISelectionFormField # ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods. ISuffixedFormField # ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods. TDefaultIdFormField # Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() . Displaying Forms # The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Structure"},{"location":"php/api/form_builder/structure/#structure-of-form-builder","text":"Forms built with form builder consist of three major structural elements listed from top to bottom: form document, form container, form field. The basis for all three elements are form nodes. The form builder API uses fluent interfaces heavily, meaning that unless a method is a getter, it generally returns the objects itself to support method chaining.","title":"Structure of Form Builder"},{"location":"php/api/form_builder/structure/#form-nodes","text":"IFormNode is the base interface that any node of a form has to implement. IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node. IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. IFormElement extends IFormNode for such elements of a form that can have a description and a label.","title":"Form Nodes"},{"location":"php/api/form_builder/structure/#iformnode","text":"IFormNode is the base interface that any node of a form has to implement and it requires the following methods: addClass($class) , addClasses(array $classes) , removeClass($class) , getClasses() , and hasClass($class) add, remove, get, and check for CSS classes of the HTML element representing the form node. If the form node consists of multiple (nested) HTML elements, the classes are generally added to the top element. static validateClass($class) is used to check if a given CSS class is valid. By default, a form node has no CSS classes. addDependency(IFormFieldDependency $dependency) , removeDependency($dependencyId) , getDependencies() , and hasDependency($dependencyId) add, remove, get, and check for dependencies of this form node on other form fields. checkDependencies() checks if all of the node\u2019s dependencies are met and returns a boolean value reflecting the check\u2019s result. The form builder dependency documentation provides more detailed information about dependencies and how they work. By default, a form node has no dependencies. attribute($name, $value = null) , removeAttribute($name) , getAttribute($name) , getAttributes() , hasAttribute($name) add, remove, get, and check for attributes of the HTML element represting the form node. The attributes are added to the same element that the CSS classes are added to. static validateAttribute($name) is used to check if a given attribute is valid. By default, a form node has no attributes. available($available = true) and isAvailable() can be used to set and check if the node is available. The availability functionality can be used to easily toggle form nodes based, for example, on options without having to create a condition to append the relevant. This way of checking availability makes it easier to set up forms. By default, every form node is available. The following aspects are important when working with availability: Unavailable fields produce no output, their value is not read, they are not validated and they are not checked for save values. Form fields are also able to mark themselves as unavailable, for example, a selection field without any options. Form containers are automatically unavailable if they contain no available children. Availability sets the static availability for form nodes that does not change during the lifetime of a form. In contrast, dependencies represent a dynamic availability for form nodes that depends on the current value of certain form fields. - cleanup() is called after the whole form is not used anymore to reset other APIs if the form fields depends on them and they expect such a reset. This method is not intended to clean up the form field\u2019s value as a new form document object is created to show a clean form. - getDocument() returns the IFormDocument object the node belongs to. (As IFormDocument extends IFormNode , form document objects simply return themselves.) - getHtml() returns the HTML representation of the node. getHtmlVariables() return template variables (in addition to the form node itself) to render the node\u2019s HTML representation. - id($id) and getId() set and get the id of the form node. Every id has to be unique within a form. getPrefixedId() returns the prefixed version of the node\u2019s id (see IFormDocument::getPrefix() and IFormDocument::prefix() ). static validateId($id) is used to check if a given id is valid. - populate() is called by IFormDocument::build() after all form nodes have been added. This method should finilize the initialization of the form node after all parent-child relations of the form document have been established. This method is needed because during the construction of a form node, it neither knows the form document it will belong to nor does it know its parent. - validate() checks, after the form is submitted, if the form node is valid. A form node with children is valid if all of its child nodes are valid. A form field is valid if its value is valid. - static create($id) is the factory method that has to be used to create new form nodes with the given id. TFormNode provides a default implementation of most of these methods.","title":"IFormNode"},{"location":"php/api/form_builder/structure/#iformchildnode","text":"IFormChildNode extends IFormNode for such elements of a form that can be a child node to a parent node and it requires the parent(IFormParentNode $parentNode) and getParent() methods used to set and get the node\u2019s parent node. TFormChildNode provides a default implementation of these two methods and also of IFormNode::getDocument() .","title":"IFormChildNode"},{"location":"php/api/form_builder/structure/#iformparentnode","text":"IFormParentNode extends IFormNode for such elements of a form that can be a parent to child nodes. Additionally, the interface also extends \\Countable and \\RecursiveIterator . The interface requires the following methods: appendChild(IFormChildNode $child) , appendChildren(array $children) , insertAfter(IFormChildNode $child, $referenceNodeId) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children either at the end or at specific positions. validateChild(IFormChildNode $child) is used to check if a given child node can be added. A child node cannot be added if it would cause an id to be used twice. children() returns the direct children of a form node. getIterator() return a recursive iterator for a form node. getNodeById($nodeId) returns the node with the given id by searching for it in the node\u2019s children and recursively in all of their children. contains($nodeId) can be used to simply check if a node with the given id exists. hasValidationErrors() checks if a form node or any of its children has a validation error (see IFormField::getValidationErrors() ). readValues() recursively calls IFormParentNode::readValues() and IFormField::readValue() on its children.","title":"IFormParentNode"},{"location":"php/api/form_builder/structure/#iformelement","text":"IFormElement extends IFormNode for such elements of a form that can have a description and a label and it requires the following methods: label($languageItem = null, array $variables = []) and getLabel() can be used to set and get the label of the form element. requiresLabel() can be checked if the form element requires a label. A label-less form element that requires a label will prevent the form from being rendered by throwing an exception. description($languageItem = null, array $variables = []) and getDescription() can be used to set and get the description of the form element.","title":"IFormElement"},{"location":"php/api/form_builder/structure/#iobjecttypeformnode","text":"IObjectTypeFormField has to be implemented by form nodes that rely on a object type of a specific object type definition in order to function. The implementing class has to implement the methods objectType($objectType) , getObjectType() , and getObjectTypeDefinition() . TObjectTypeFormNode provides a default implementation of these three methods.","title":"IObjectTypeFormNode"},{"location":"php/api/form_builder/structure/#customformnode","text":"CustomFormNode is a form node whose contents can be set directly via content($content) . This class should generally not be relied on. Instead, TemplateFormNode should be used.","title":"CustomFormNode"},{"location":"php/api/form_builder/structure/#templateformnode","text":"TemplateFormNode is a form node whose contents are read from a template. TemplateFormNode has the following additional methods: application($application) and getApplicaton() can be used to set and get the abbreviation of the application the shown template belongs to. If no template has been set explicitly, getApplicaton() returns wcf . templateName($templateName) and getTemplateName() can be used to set and get the name of the template containing the node contents. If no template has been set and the node is rendered, an exception will be thrown. variables(array $variables) and getVariables() can be used to set and get additional variables passed to the template.","title":"TemplateFormNode"},{"location":"php/api/form_builder/structure/#form-document","text":"A form document object represents the form as a whole and has to implement the IFormDocument interface. WoltLab Suite provides a default implementation with the FormDocument class. IFormDocument should not be implemented directly but instead FormDocument should be extended to avoid issues if the IFormDocument interface changes in the future. IFormDocument extends IFormParentNode and requires the following additional methods: action($action) and getAction() can be used set and get the action attribute of the <form> HTML element. addButton(IFormButton $button) and getButtons() can be used add and get form buttons that are shown at the bottom of the form. addDefaultButton($addDefaultButton) and hasDefaultButton() can be used to set and check if the form has the default button which is added by default unless specified otherwise. Each implementing class may define its own default button. FormDocument has a button with id submitButton , label wcf.global.button.submit , access key s , and CSS class buttonPrimary as its default button. ajax($ajax) and isAjax() can be used to set and check if the form document is requested via an AJAX request or processes data via an AJAX request. These methods are helpful for form fields that behave differently when providing data via AJAX. build() has to be called once after all nodes have been added to this document to trigger IFormNode::populate() . formMode($formMode) and getFormMode() sets the form mode. Possible form modes are: IFormDocument::FORM_MODE_CREATE has to be used when the form is used to create a new object. IFormDocument::FORM_MODE_UPDATE has to be used when the form is used to edit an existing object. getData() returns the array containing the form data and which is passed as the $parameters argument of the constructor of a database object action object. getDataHandler() returns the data handler for this document that is used to process the field data into a parameters array for the constructor of a database object action object. getEnctype() returns the encoding type of the form. If the form contains a IFileFormField , multipart/form-data is returned, otherwise null is returned. loadValues(array $data, IStorableObject $object) is used when editing an existing object to set the form field values by calling IFormField::loadValue() for all form fields. Additionally, the form mode is set to IFormDocument::FORM_MODE_UPDATE . 5.4+ markRequiredFields(bool $markRequiredFields = true): self and marksRequiredFields(): bool can be used to set and check whether fields that are required are marked (with an asterisk in the label) in the output. method($method) and getMethod() can be used to set and get the method attribute of the <form> HTML element. By default, the method is post . prefix($prefix) and getPrefix() can be used to set and get a global form prefix that is prepended to form elements\u2019 names and ids to avoid conflicts with other forms. By default, the prefix is an empty string. If a prefix of foo is set, getPrefix() returns foo_ (additional trailing underscore). requestData(array $requestData) , getRequestData($index = null) , and hasRequestData($index = null) can be used to set, get and check for specific request data. In most cases, the relevant request data is the $_POST array. In default AJAX requests handled by database object actions, however, the request data generally is in AbstractDatabaseObjectAction::$parameters . By default, $_POST is the request data. The last aspect is relevant for DialogFormDocument objects. DialogFormDocument is a specialized class for forms in dialogs that, in contrast to FormDocument do not require an action to be set. Additionally, DialogFormDocument provides the cancelable($cancelable = true) and isCancelable() methods used to determine if the dialog from can be canceled. By default, dialog forms are cancelable.","title":"Form Document"},{"location":"php/api/form_builder/structure/#form-button","text":"A form button object represents a button shown at the end of the form that, for example, submits the form. Every form button has to implement the IFormButton interface that extends IFormChildNode and IFormElement . IFormButton requires four methods to be implemented: accessKey($accessKey) and getAccessKey() can be used to set and get the access key with which the form button can be activated. By default, form buttons have no access key set. submit($submitButton) and isSubmit() can be used to set and check if the form button is a submit button. A submit button is an input[type=submit] element. Otherwise, the button is a button element.","title":"Form Button"},{"location":"php/api/form_builder/structure/#form-container","text":"A form container object represents a container for other form containers or form field directly. Every form container has to implement the IFormContainer interface which requires the following method: loadValues(array $data, IStorableObject $object) is called by IFormDocument::loadValuesFromObject() to inform the container that object data is loaded. This method is not intended to generally call IFormField::loadValues() on its form field children as these methods are already called by IFormDocument::loadValuesFromObject() . This method is intended for specialized form containers with more complex logic. There are multiple default container implementations: FormContainer is the default implementation of IFormContainer . TabMenuFormContainer represents the container of tab menu, while TabFormContainer represents a tab of a tab menu and TabTabMenuFormContainer represents a tab of a tab menu that itself contains a tab menu. The children of RowFormContainer are shown in a row and should use col-* classes. The children of RowFormFieldContainer are also shown in a row but does not show the labels and descriptions of the individual form fields. Instead of the individual labels and descriptions, the container's label and description is shown and both span all of fields. SuffixFormFieldContainer can be used for one form field with a second selection form field used as a suffix. The methods of the interfaces that FormContainer is implementing are well documented, but here is a short overview of the most important methods when setting up a form or extending a form with an event listener: appendChild(IFormChildNode $child) , appendChildren(array $children) , and insertBefore(IFormChildNode $child, $referenceNodeId) are used to insert new children into the form container. description($languageItem = null, array $variables = []) and label($languageItem = null, array $variables = []) are used to set the description and the label or title of the form container.","title":"Form Container"},{"location":"php/api/form_builder/structure/#form-field","text":"A form field object represents a concrete form field that allows entering data. Every form field has to implement the IFormField interface which extends IFormChildNode and IFormElement . IFormField requires the following additional methods: addValidationError(IFormFieldValidationError $error) and getValidationErrors() can be used to get and set validation errors of the form field (see form validation ). addValidator(IFormFieldValidator $validator) , getValidators() , removeValidator($validatorId) , and hasValidator($validatorId) can be used to get, set, remove, and check for validators for the form field (see form validation ). getFieldHtml() returns the field's HTML output without the surrounding dl structure. objectProperty($objectProperty) and getObjectProperty() can be used to get and set the object property that the field represents. When setting the object property is set to an empty string, the previously set object property is unset. If no object property has been set, the field\u2019s (non-prefixed) id is returned. The object property allows having different fields (requiring different ids) that represent the same object property which is handy when available options of the field\u2019s value depend on another field. Having object property allows to define different fields for each value of the other field and to use form field dependencies to only show the appropriate field. - readValue() reads the form field value from the request data after the form is submitted. - required($required = true) and isRequired() can be used to determine if the form field has to be filled out. By default, form fields do not have to be filled out. - value($value) and getSaveValue() can be used to get and set the value of the form field to be used outside of the context of forms. getValue() , in contrast, returns the internal representation of the form field\u2019s value. In general, the internal representation is only relevant when validating the value in additional validators. loadValue(array $data, IStorableObject $object) extracts the form field value from the given data array (and additional, non-editable data from the object if the field needs them). AbstractFormField provides default implementations of many of the listed methods above and should be extended instead of implementing IFormField directly. An overview of the form fields provided by default can be found here .","title":"Form Field"},{"location":"php/api/form_builder/structure/#form-field-interfaces-and-traits","text":"WoltLab Suite Core provides a variety of interfaces and matching traits with default implementations for several common features of form fields:","title":"Form Field Interfaces and Traits"},{"location":"php/api/form_builder/structure/#iattributeformfield","text":"Only available since version 5.4. IAttributeFormField has to be implemented by form fields for which attributes can be added to the actual form element (in addition to adding attributes to the surrounding element via the attribute-related methods of IFormNode ). The implementing class has to implement the methods fieldAttribute(string $name, string $value = null): self and getFieldAttribute(string $name): self / getFieldAttributes(): array , which are used to add and get the attributes, respectively. Additionally, hasFieldAttribute(string $name): bool has to implemented to check if a certain attribute is present, removeFieldAttribute(string $name): self to remove an attribute, and static validateFieldAttribute(string $name) to check if the attribute is valid for this specific class. TAttributeFormField provides a default implementation of these methods and TInputAttributeFormField specializes the trait for input -based form fields. These two traits also ensure that if a specific interface that handles a specific attribute is implemented, like IAutoCompleteFormField handling autocomplete , this attribute cannot be set with this API. Instead, the dedicated API provided by the relevant interface has to be used.","title":"IAttributeFormField"},{"location":"php/api/form_builder/structure/#iautocompleteformfield","text":"Only available since version 5.4. IAutoCompleteFormField has to be implemented by form fields that support the autocomplete attribute . The implementing class has to implement the methods autoComplete(?string $autoComplete): self and getAutoComplete(): ?string , which are used to set and get the autocomplete value, respectively. TAutoCompleteFormField provides a default implementation of these two methods and TTextAutoCompleteFormField specializes the trait for text form fields. When using TAutoCompleteFormField , you have to implement the getValidAutoCompleteTokens(): array method which returns all valid autocomplete tokens.","title":"IAutoCompleteFormField"},{"location":"php/api/form_builder/structure/#iautofocusformfield","text":"IAutoFocusFormField has to be implemented by form fields that can be auto-focused. The implementing class has to implement the methods autoFocus($autoFocus = true) and isAutoFocused() . By default, form fields are not auto-focused. TAutoFocusFormField provides a default implementation of these two methods.","title":"IAutoFocusFormField"},{"location":"php/api/form_builder/structure/#icssclassformfield","text":"Only available since version 5.4. ICssClassFormField has to be implemented by form fields for which CSS classes can be added to the actual form element (in addition to adding CSS classes to the surrounding element via the class-related methods of IFormNode ). The implementing class has to implement the methods addFieldClass(string $class): self / addFieldClasses(array $classes): self and getFieldClasses(): array , which are used to add and get the CSS classes, respectively. Additionally, hasFieldClass(string $class): bool has to implemented to check if a certain CSS class is present and removeFieldClass(string $class): self to remove a CSS class. TCssClassFormField provides a default implementation of these methods.","title":"ICssClassFormField"},{"location":"php/api/form_builder/structure/#ifileformfield","text":"IFileFormField has to be implemented by every form field that uploads files so that the enctype attribute of the form document is multipart/form-data (see IFormDocument::getEnctype() ).","title":"IFileFormField"},{"location":"php/api/form_builder/structure/#ifilterableselectionformfield","text":"IFilterableSelectionFormField extends ISelectionFormField by the possibilty for users when selecting the value(s) to filter the list of available options. The implementing class has to implement the methods filterable($filterable = true) and isFilterable() . TFilterableSelectionFormField provides a default implementation of these two methods.","title":"IFilterableSelectionFormField"},{"location":"php/api/form_builder/structure/#ii18nformfield","text":"II18nFormField has to be implemented by form fields if the form field value can be entered separately for all available languages. The implementing class has to implement the following methods: i18n($i18n = true) and isI18n() can be used to set whether a specific instance of the class actually supports multilingual input. i18nRequired($i18nRequired = true) and isI18nRequired() can be used to set whether a specific instance of the class requires separate values for all languages. languageItemPattern($pattern) and getLanguageItemPattern() can be used to set the pattern/regular expression for the language item used to save the multilingual values. hasI18nValues() and hasPlainValue() check if the current value is a multilingual or monolingual value. TI18nFormField provides a default implementation of these eight methods and additional default implementations of some of the IFormField methods. If multilingual input is enabled for a specific form field, classes using TI18nFormField register a custom form field data processor to add the array with multilingual input into the $parameters array directly using {$objectProperty}_i18n as the array key. If multilingual input is enabled but only a monolingual value is entered, the custom form field data processor does nothing and the form field\u2019s value is added by the DefaultFormDataProcessor into the data sub-array of the $parameters array. TI18nFormField already provides a default implementation of IFormField::validate() .","title":"II18nFormField"},{"location":"php/api/form_builder/structure/#iimmutableformfield","text":"IImmutableFormField has to be implemented by form fields that support being displayed but whose value cannot be changed. The implementing class has to implement the methods immutable($immutable = true) and isImmutable() that can be used to determine if the value of the form field is mutable or immutable. By default, form field are mutable.","title":"IImmutableFormField"},{"location":"php/api/form_builder/structure/#iinputmodeformfield","text":"Only available since version 5.4. IInputModeFormField has to be implemented by form fields that support the inputmode attribute . The implementing class has to implement the methods inputMode(?string $inputMode): self and getInputMode(): ?string , which are used to set and get the input mode, respectively. TInputModeFormField provides a default implementation of these two methods.","title":"IInputModeFormField"},{"location":"php/api/form_builder/structure/#imaximumformfield","text":"IMaximumFormField has to be implemented by form fields if the entered value must have a maximum value. The implementing class has to implement the methods maximum($maximum = null) and getMaximum() . A maximum of null signals that no maximum value has been set. TMaximumFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually.","title":"IMaximumFormField"},{"location":"php/api/form_builder/structure/#imaximumlengthformfield","text":"IMaximumLengthFormField has to be implemented by form fields if the entered value must have a maximum length. The implementing class has to implement the methods maximumLength($maximumLength = null) , getMaximumLength() , and validateMaximumLength($text, Language $language = null) . A maximum length of null signals that no maximum length has been set. TMaximumLengthFormField provides a default implementation of these two methods. The implementing class has to validate the entered value against the maximum value manually by calling validateMaximumLength() .","title":"IMaximumLengthFormField"},{"location":"php/api/form_builder/structure/#iminimumformfield","text":"IMinimumFormField has to be implemented by form fields if the entered value must have a minimum value. The implementing class has to implement the methods minimum($minimum = null) and getMinimum() . A minimum of null signals that no minimum value has been set. TMinimumFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually.","title":"IMinimumFormField"},{"location":"php/api/form_builder/structure/#iminimumlengthformfield","text":"IMinimumLengthFormField has to be implemented by form fields if the entered value must have a minimum length. The implementing class has to implement the methods minimumLength($minimumLength = null) , getMinimumLength() , and validateMinimumLength($text, Language $language = null) . A minimum length of null signals that no minimum length has been set. TMinimumLengthFormField provides a default implementation of these three methods. The implementing class has to validate the entered value against the minimum value manually by calling validateMinimumLength() .","title":"IMinimumLengthFormField"},{"location":"php/api/form_builder/structure/#imultipleformfield","text":"IMinimumLengthFormField has to be implemented by form fields that support selecting or setting multiple values. The implementing class has to implement the following methods: multiple($multiple = true) and allowsMultiple() can be used to set whether a specific instance of the class actually should support multiple values. By default, multiple values are not supported. minimumMultiples($minimum) and getMinimumMultiples() can be used to set the minimum number of values that have to be selected/entered. By default, there is no required minimum number of values. maximumMultiples($minimum) and getMaximumMultiples() can be used to set the maximum number of values that have to be selected/entered. By default, there is no maximum number of values. IMultipleFormField::NO_MAXIMUM_MULTIPLES is returned if no maximum number of values has been set and it can also be used to unset a previously set maximum number of values. TMultipleFormField provides a default implementation of these six methods and classes using TMultipleFormField register a custom form field data processor to add the HtmlInputProcessor object with the text into the $parameters array directly using {$objectProperty}_htmlInputProcessor as the array key. The implementing class has to validate the values against the minimum and maximum number of values manually.","title":"IMultipleFormField"},{"location":"php/api/form_builder/structure/#inullableformfield","text":"INullableFormField has to be implemented by form fields that support null as their (empty) value. The implementing class has to implement the methods nullable($nullable = true) and isNullable() . TNullableFormField provides a default implementation of these two methods. null should be returned by IFormField::getSaveValue() is the field is considered empty and the form field has been set as nullable.","title":"INullableFormField"},{"location":"php/api/form_builder/structure/#ipackagesformfield","text":"IPackagesFormField has to be implemented by form fields that, in some way, considers packages whose ids may be passed to the field object. The implementing class has to implement the methods packageIDs(array $packageIDs) and getPackageIDs() . TPackagesFormField provides a default implementation of these two methods.","title":"IPackagesFormField"},{"location":"php/api/form_builder/structure/#ipatternformfield","text":"Only available since version 5.4. IPatternFormField has to be implemented by form fields that support the pattern attribute . The implementing class has to implement the methods pattern(?string $pattern): self and getPattern(): ?string , which are used to set and get the pattern, respectively. TPatternFormField provides a default implementation of these two methods.","title":"IPatternFormField"},{"location":"php/api/form_builder/structure/#iplaceholderformfield","text":"IPlaceholderFormField has to be implemented by form fields that support a placeholder value for empty fields. The implementing class has to implement the methods placeholder($languageItem = null, array $variables = []) and getPlaceholder() . TPlaceholderFormField provides a default implementation of these two methods.","title":"IPlaceholderFormField"},{"location":"php/api/form_builder/structure/#iselectionformfield","text":"ISelectionFormField has to be implemented by form fields with a predefined set of possible values. The implementing class has to implement the getter and setter methods options($options, $nestedOptions = false, $labelLanguageItems = true) and getOptions() and additionally two methods related to nesting, i.e. whether the selectable options have a hierarchy: supportsNestedOptions() and getNestedOptions() . TSelectionFormField provides a default implementation of these four methods.","title":"ISelectionFormField"},{"location":"php/api/form_builder/structure/#isuffixedformfield","text":"ISuffixedFormField has to be implemented by form fields that support supports displaying a suffix behind the actual input field. The implementing class has to implement the methods suffix($languageItem = null, array $variables = []) and getSuffix() . TSuffixedFormField provides a default implementation of these two methods.","title":"ISuffixedFormField"},{"location":"php/api/form_builder/structure/#tdefaultidformfield","text":"Form fields that have a default id have to use TDefaultIdFormField and have to implement the method getDefaultId() .","title":"TDefaultIdFormField"},{"location":"php/api/form_builder/structure/#displaying-forms","text":"The only thing to do in a template to display the whole form including all of the necessary JavaScript is to put { @ $form -> getHtml () } into the template file at the relevant position.","title":"Displaying Forms"},{"location":"php/api/form_builder/validation_data/","text":"Form Validation and Form Data # Form Validation # Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface. IFormFieldValidationError / FormFieldValidationError # IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method. IFormFieldValidator / FormFieldValidator # IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method. Form Data # After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler. IFormDataHandler / FormDataHandler # IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly. IFormDataProcessor / DefaultFormDataProcessor # IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array. Additional Data Processors # CustomFormDataProcessor # As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged. VoidFormDataProcessor # Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"Validation and Data"},{"location":"php/api/form_builder/validation_data/#form-validation-and-form-data","text":"","title":"Form Validation and Form Data"},{"location":"php/api/form_builder/validation_data/#form-validation","text":"Every form field class has to implement IFormField::validate() according to their internal logic of what constitutes a valid value. If a certain constraint for the value is no met, a form field validation error object is added to the form field. Form field validation error classes have to implement the interface IFormFieldValidationError . In addition to intrinsic validations like checking the length of the value of a text form field, in many cases, there are additional constraints specific to the form like ensuring that the text is not already used by a different object of the same database object class. Such additional validations can be added to (and removed from) the form field via implementations of the IFormFieldValidator interface.","title":"Form Validation"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidationerror-formfieldvalidationerror","text":"IFormFieldValidationError requires the following methods: __construct($type, $languageItem = null, array $information = []) creates a new validation error object for an error with the given type and message stored in the given language items. The information array is used when generating the error message. getHtml() returns the HTML element representing the error that is shown to the user. getMessage() returns the error message based on the language item and information array given in the constructor. getInformation() and getType() are getters for the first and third parameter of the constructor. FormFieldValidationError is a default implementation of the interface that shows the error in an small.innerError HTML element below the form field. Form field validation errors are added to form fields via the IFormField::addValidationError(IFormFieldValidationError $error) method.","title":"IFormFieldValidationError / FormFieldValidationError"},{"location":"php/api/form_builder/validation_data/#iformfieldvalidator-formfieldvalidator","text":"IFormFieldValidator requires the following methods: __construct($id, callable $validator) creates a new validator with the given id that passes the validated form field to the given callable that does the actual validation. static validateId($id) is used to check if the given id is valid. __invoke(IFormField $field) is used when the form field is validated to execute the validator. getId() returns the id of the validator. FormFieldValidator is a default implementation of the interface. Form field validators are added to form fields via the addValidator(IFormFieldValidator $validator) method.","title":"IFormFieldValidator / FormFieldValidator"},{"location":"php/api/form_builder/validation_data/#form-data","text":"After a form is successfully validated, the data of the form fields (returned by IFormDocument::getData() ) have to be extracted which is the job of the IFormDataHandler object returned by IFormDocument::getDataHandler() . Form data handlers themselves, however, are only iterating through all IFormDataProcessor instances that have been registered with the data handler.","title":"Form Data"},{"location":"php/api/form_builder/validation_data/#iformdatahandler-formdatahandler","text":"IFormDataHandler requires the following methods: addProcessor(IFormDataProcessor $processor) adds a new data processor to the data handler. getFormData(IFormDocument $document) returns the data of the given form by applying all registered data handlers on the form. getObjectData(IFormDocument $document, IStorableObject $object) returns the data of the given object which will be used to populate the form field values of the given form. FormDataHandler is the default implementation of this interface and should also be extended instead of implementing the interface directly.","title":"IFormDataHandler / FormDataHandler"},{"location":"php/api/form_builder/validation_data/#iformdataprocessor-defaultformdataprocessor","text":"IFormDataProcessor requires the following methods: processFormData(IFormDocument $document, array $parameters) is called by IFormDataHandler::getFormData() . The method processes the given parameters array and returns the processed version. processObjectData(IFormDocument $document, array $data, IStorableObject $object) is called by IFormDataHandler::getObjectData() . The method processes the given object data array and returns the processed version. When FormDocument creates its FormDataHandler instance, it automatically registers an DefaultFormDataProcessor object as the first data processor. DefaultFormDataProcessor puts the save value of all form fields that are available and have a save value into $parameters['data'] using the form field\u2019s object property as the array key. IFormDataProcessor should not be implemented directly. Instead, AbstractFormDataProcessor should be extended. All form data is put into the data sub-array so that the whole $parameters array can be passed to a database object action object that requires the actual database object data to be in the data sub-array.","title":"IFormDataProcessor / DefaultFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#additional-data-processors","text":"","title":"Additional Data Processors"},{"location":"php/api/form_builder/validation_data/#customformdataprocessor","text":"As mentioned above, the data in the data sub-array is intended to directly create or update the database object with. As these values are used in the database query directly, these values cannot contain arrays. Several form fields, however, store and return their data in form of arrays. Thus, this data cannot be returned by IFormField::getSaveValue() so that IFormField::hasSaveValue() returns false and the form field\u2019s data is not collected by the standard DefaultFormDataProcessor object. Instead, such form fields register a CustomFormDataProcessor in their IFormField::populate() method that inserts the form field value into the $parameters array directly. This way, the relevant database object action method has access to the data to save it appropriately. The constructor of CustomFormDataProcessor requires an id (that is primarily used in error messages during the validation of the second parameter) and callables for IFormDataProcessor::processFormData() and IFormDataProcessor::processObjectData() which are passed the same parameters as the IFormDataProcessor methods. Only one of the callables has to be given, the other one then defaults to simply returning the relevant array unchanged.","title":"CustomFormDataProcessor"},{"location":"php/api/form_builder/validation_data/#voidformdataprocessor","text":"Some form fields might only exist to toggle the visibility of other form fields (via dependencies) but the data of form field itself is irrelevant. As DefaultFormDataProcessor collects the data of all form fields, an additional data processor in the form of a VoidFormDataProcessor can be added whose constructor __construct($property, $isDataProperty = true) requires the name of the relevant object property/form id and whether the form field value is stored in the data sub-array or directory in the $parameters array. When the data processor is invoked, it checks whether the relevant entry in the $parameters array exists and voids it by removing it from the array.","title":"VoidFormDataProcessor"},{"location":"tutorial/series/overview/","text":"Tutorial Series # In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Overview"},{"location":"tutorial/series/overview/#tutorial-series","text":"In this tutorial series, we will code a package that allows administrators to create a registry of people. In this context, \"people\" does not refer to users registered on the website but anybody living, dead or fictional. We will start this tutorial series by creating a base structure for the package and then continue by adding further features step by step using different APIs. Note that in the context of this example, not every added feature might make perfect sense but the goal of this tutorial is not to create a useful package but to introduce you to WoltLab Suite. Part 1: Base Structure Part 2: Event Listeners and Template Listeners Part 3: Person Page and Comments","title":"Tutorial Series"},{"location":"tutorial/series/part_1/","text":"Tutorial Series Part 1: Base Structure # In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions. Package Functionality # The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml Person Modeling # Database Table # As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL ); Database Object # Person # In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class. PersonAction # <? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP. PersonEditor # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name. PersonList # <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class. ACP # Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu. ACP Menu # We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value. People List # To list the people in the ACP, we need a PersonListPage class and a personList template. PersonListPage # <? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns. personList.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people. Person Add Form # Like the person list, the form to add new people requires a controller class and a template. PersonAddForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases. personAdd.tpl # { include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted. Person Edit Form # As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing. PersonEditForm # <? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\" Frontend # For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu. page.xml # First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page . menuItem.xml # Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered. People List # As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories. PersonListPage # <? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default. personList.tpl # { capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events. userGroupOption.xml # We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 . package.xml # Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"Part 1"},{"location":"tutorial/series/part_1/#tutorial-series-part-1-base-structure","text":"In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions.","title":"Tutorial Series Part 1: Base Structure"},{"location":"tutorial/series/part_1/#package-functionality","text":"The package should provide the following possibilities/functions: Sortable list of all people in the ACP Ability to add, edit and delete people in the ACP Restrict the ability to add, edit and delete people (in short: manage people) in the ACP Sortable list of all people in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_1/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , acpMenu package installation plugin , file package installation plugin , language package installation plugin , menuItem package installation plugin , page package installation plugin , sql package installation plugin , template package installation plugin , userGroupOption package installation plugin , use database objects , create pages and use templates .","title":"Used Components"},{"location":"tutorial/series/part_1/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml","title":"Package Structure"},{"location":"tutorial/series/part_1/#person-modeling","text":"","title":"Person Modeling"},{"location":"tutorial/series/part_1/#database-table","text":"As the first step, we have to model the people we want to manage with this package. As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person. Thus, the database table we will store the people in only contains three columns: personID is the unique numeric identifier of each person created, firstName contains the first name of the person, lastName contains the last name of the person. The first file for our package is the install.sql file used to create such a database table during package installation: DROP TABLE IF EXISTS wcf1_person ; CREATE TABLE wcf1_person ( personID INT ( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , firstName VARCHAR ( 255 ) NOT NULL , lastName VARCHAR ( 255 ) NOT NULL );","title":"Database Table"},{"location":"tutorial/series/part_1/#database-object","text":"","title":"Database Object"},{"location":"tutorial/series/part_1/#person","text":"In our PHP code, each person will be represented by an object of the following class: <? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObject ; use wcf\\system\\request\\IRouteController ; /** * Represents a person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @property-read integer $personID unique id of the person * @property-read string $firstName first name of the person * @property-read string $lastName last name of the person */ class Person extends DatabaseObject implements IRouteController { /** * Returns the first and last name of the person if a person object is treated as a string. * * @return string */ public function __toString () { return $this -> getTitle (); } /** * @inheritDoc */ public function getTitle () { return $this -> firstName . ' ' . $this -> lastName ; } } The important thing here is that Person extends DatabaseObject . Additionally, we implement the IRouteController interface, which allows us to use Person objects to create links, and we implement PHP's magic __toString() method for convenience. For every database object, you need to implement three additional classes: an action class, an editor class and a list class.","title":"Person"},{"location":"tutorial/series/part_1/#personaction","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\AbstractDatabaseObjectAction ; /** * Executes person-related actions. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person create() * @method PersonEditor[] getObjects() * @method PersonEditor getSingleObject() */ class PersonAction extends AbstractDatabaseObjectAction { /** * @inheritDoc */ protected $permissionsDelete = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ protected $requireACP = [ 'delete' ]; } This implementation of AbstractDatabaseObjectAction is very basic and only sets the $permissionsDelete and $requireACP properties. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. $permissionsDelete has to be set to the permission needed in order to delete a person. We will later use the userGroupOption package installation plugin to create the admin.content.canManagePeople permission. $requireACP restricts deletion of people to the ACP.","title":"PersonAction"},{"location":"tutorial/series/part_1/#personeditor","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectEditor ; /** * Provides functions to edit people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method static Person create(array $parameters = []) * @method Person getDecoratedObject() * @mixin Person */ class PersonEditor extends DatabaseObjectEditor { /** * @inheritDoc */ protected static $baseClass = Person :: class ; } This implementation of DatabaseObjectEditor fulfills the minimum requirement for a database object editor: setting the static $baseClass property to the database object class name.","title":"PersonEditor"},{"location":"tutorial/series/part_1/#personlist","text":"<? php namespace wcf\\data\\person ; use wcf\\data\\DatabaseObjectList ; /** * Represents a list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Data\\Person * * @method Person current() * @method Person[] getObjects() * @method Person|null search($objectID) * @property Person[] $objects */ class PersonList extends DatabaseObjectList {} Due to the default implementation of DatabaseObjectList , our PersonList class just needs to extend it and everything else is either automatically set by the code of DatabaseObjectList or, in the case of properties and methods, provided by that class.","title":"PersonList"},{"location":"tutorial/series/part_1/#acp","text":"Next, we will take care of the controllers and views for the ACP. In total, we need three each: page to list people, form to add people, and form to edit people. Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu.","title":"ACP"},{"location":"tutorial/series/part_1/#acp-menu","text":"We need to create three menu items: a \u201cparent\u201d menu item on the second level of the ACP menu item tree, a third level menu item for the people list page, and a fourth level menu item for the form to add new people. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/acpMenu.xsd\" > <import> <acpmenuitem name= \"wcf.acp.menu.link.person\" > <parent> wcf.acp.menu.link.content </parent> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.list\" > <controller> wcf\\acp\\page\\PersonListPage </controller> <parent> wcf.acp.menu.link.person </parent> <permissions> admin.content.canManagePeople </permissions> </acpmenuitem> <acpmenuitem name= \"wcf.acp.menu.link.person.add\" > <controller> wcf\\acp\\form\\PersonAddForm </controller> <parent> wcf.acp.menu.link.person.list </parent> <permissions> admin.content.canManagePeople </permissions> <icon> fa-plus </icon> </acpmenuitem> </import> </data> We choose wcf.acp.menu.link.content as the parent menu item for the first menu item wcf.acp.menu.link.person because the people we are managing is just one form of content. The fourth level menu item wcf.acp.menu.link.person.add will only be shown as an icon and thus needs an additional element icon which takes a FontAwesome icon class as value.","title":"ACP Menu"},{"location":"tutorial/series/part_1/#people-list","text":"To list the people in the ACP, we need a PersonListPage class and a personList template.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage","text":"<? php namespace wcf\\acp\\page ; use wcf\\data\\person\\PersonList ; use wcf\\page\\SortablePage ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.list' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal: We need to set the active ACP menu item via the $activeMenuItem . $neededPermissions contains a list of permissions of which the user needs to have at least one in order to see the person list. We use the same permission for both the menu item and the page. The database object list class whose name is provided via $objectListClassName and that handles fetching the people from database is the PersonList class, which we have already created. To validate the sort field passed with the request, we set $validSortFields to the available database table columns.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.list' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person.list { /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { hascontent } <div class=\"paginationTop\"> { content }{ pages print = true assign = pagesLinks controller = \"PersonList\" link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" }{ /content } </div> { /hascontent } { if $objects | count } <div class=\"section tabularBox\" id=\"personTableContainer\"> <table class=\"table\"> <thead> <tr> <th class=\"columnID columnPersonID { if $sortField == 'personID' } active { @ $sortOrder }{ /if } \" colspan=\"2\"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=personID&sortOrder= { if $sortField == 'personID' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.global.objectID { /lang } </a></th> <th class=\"columnTitle columnFirstName { if $sortField == 'firstName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=firstName&sortOrder= { if $sortField == 'firstName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.firstName { /lang } </a></th> <th class=\"columnTitle columnLastName { if $sortField == 'lastName' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=lastName&sortOrder= { if $sortField == 'lastName' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.lastName { /lang } </a></th> { event name = 'columnHeads' } </tr> </thead> <tbody class=\"jsReloadPageWhenEmpty\"> { foreach from = $objects item = person } <tr class=\"jsPersonRow\"> <td class=\"columnIcon\"> <a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \" title=\" { lang } wcf.global.button.edit { /lang } \" class=\"jsTooltip\"><span class=\"icon icon16 fa-pencil\"></span></a> <span class=\"icon icon16 fa-times jsDeleteButton jsTooltip pointer\" title=\" { lang } wcf.global.button.delete { /lang } \" data-object-id=\" { @ $person -> personID } \" data-confirm-message-html=\" { lang __encode = true } wcf.acp.person.delete.confirmMessage { /lang } \"></span> { event name = 'rowButtons' } </td> <td class=\"columnID\"> { # $person -> personID } </td> <td class=\"columnTitle columnFirstName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> firstName } </a></td> <td class=\"columnTitle columnLastName\"><a href=\" { link controller = 'PersonEdit' object = $person }{ /link } \"> { $person -> lastName } </a></td> { event name = 'columns' } </tr> { /foreach } </tbody> </table> </div> <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } <nav class=\"contentFooterNavigation\"> <ul> <li><a href=\" { link controller = 'PersonAdd' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-plus\"></span> <span> { lang } wcf.acp.menu.link.person.add { /lang } </span></a></li> { event name = 'contentFooterNavigation' } </ul> </nav> </footer> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <script data-relocate=\"true\"> $(function() { new WCF . Action . Delete ( 'wcf\\\\data\\\\person\\\\PersonAction' , '.jsPersonRow' ); } ); </script> { include file = 'footer' } We will go piece by piece through the template code: We include the header template and set the page title wcf.acp.person.list . You have to include this template for every page! We set the content header and additional provide a button to create a new person in the content header navigation. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the pages template plugin. The {hascontent}{content}{/content}{/hascontent} construct ensures the .paginationTop element is only shown if the pages template plugin has a return value, thus if a pagination is necessary. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist. Otherwise, an info box is displayed using the generic wcf.global.noItems language item. The $objects template variable is automatically assigned by wcf\\page\\MultipleLinkPage and contains the PersonList object used to read the people from database. The table itself consists of a thead and a tbody element and is extendable with more columns using the template events columnHeads and columns . In general, every table should provide these events. The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event rowButtons ) and that the second column contains the ID of the person. The table can be sorted by clicking on the head of each column. The used variables $sortField and $sortOrder are automatically assigned to the template by SortablePage . 1. The .contentFooter element is only shown if people exist as it basically repeats the .contentHeaderNavigation and .paginationTop element. 1. The JavaScript code here fulfills two duties: Handling clicks on the delete icons and forwarding the requests via AJAX to the PersonAction class, and setting up some code that triggers if all people shown on the current page are deleted via JavaScript to either reload the page or show the wcf.global.noItems info box. 1. Lastly, the footer template is included that terminates the page. You also have to include this template for every page! Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#person-add-form","text":"Like the person list, the form to add new people requires a controller class and a template.","title":"Person Add Form"},{"location":"tutorial/series/part_1/#personaddform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\request\\LinkHandler ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Shows the form to create a new person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonAddForm extends AbstractForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person.add' ; /** * first name of the person * @var string */ public $firstName = '' ; /** * last name of the person * @var string */ public $lastName = '' ; /** * @inheritDoc */ public $neededPermissions = [ 'admin.content.canManagePeople' ]; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'add' , 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]); } /** * @inheritDoc */ public function readFormParameters () { parent :: readFormParameters (); if ( isset ( $_POST [ 'firstName' ])) $this -> firstName = StringUtil :: trim ( $_POST [ 'firstName' ]); if ( isset ( $_POST [ 'lastName' ])) $this -> lastName = StringUtil :: trim ( $_POST [ 'lastName' ]); } /** * @inheritDoc */ public function save () { parent :: save (); $this -> objectAction = new PersonAction ([], 'create' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $returnValues = $this -> objectAction -> executeAction (); $this -> saved (); // reset values $this -> firstName = '' ; $this -> lastName = '' ; // show success message WCF :: getTPL () -> assign ([ 'success' => true , 'objectEditLink' => LinkHandler :: getInstance () -> getControllerLink ( PersonEditForm :: class , [ 'id' => $returnValues [ 'returnValues' ] -> personID ]), ]); } /** * @inheritDoc */ public function validate () { parent :: validate (); // validate first name if ( empty ( $this -> firstName )) { throw new UserInputException ( 'firstName' ); } if ( mb_strlen ( $this -> firstName ) > 255 ) { throw new UserInputException ( 'firstName' , 'tooLong' ); } // validate last name if ( empty ( $this -> lastName )) { throw new UserInputException ( 'lastName' ); } if ( mb_strlen ( $this -> lastName ) > 255 ) { throw new UserInputException ( 'lastName' , 'tooLong' ); } } } The properties here consist of two types: the \u201chousekeeping\u201d properties $activeMenuItem and $neededPermissions , which fulfill the same roles as for PersonListPage , and the \u201cdata\u201d properties $firstName and $lastName , which will contain the data entered by the user of the person to be created. Now, let's go through each method in execution order: readFormParameters() is called after the form has been submitted and reads the entered first and last name and sanitizes the values by calling StringUtil::trim() . validate() is called after the form has been submitted and is used to validate the input data. In case of invalid data, the method is expected to throw a UserInputException . Here, the validation for first and last name is the same and quite basic: We check that any name has been entered and that it is not longer than the database table column permits. save() is called after the form has been submitted and the entered data has been validated and it creates the new person via PersonAction . Please note that we do not just pass the first and last name to the action object but merge them with the $this->additionalFields array which can be used by event listeners of plugins to add additional data. After creating the object, the saved() method is called which fires an event for plugins and the data properties are cleared so that the input fields on the page are empty so that another new person can be created. Lastly, a success variable is assigned to the template which will show a message that the person has been successfully created. assignVariables() assigns the values of the \u201cdata\u201d properties to the template and additionally assigns an action variable. This action variable will be used in the template to distinguish between adding a new person and editing an existing person so that which minimal adjustments, we can use the template for both cases.","title":"PersonAddForm"},{"location":"tutorial/series/part_1/#personaddtpl","text":"{ include file = 'header' pageTitle = 'wcf.acp.person.' | concat : $action } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\"> { lang } wcf.acp.person. { $action }{ /lang } </h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> <li><a href=\" { link controller = 'PersonList' }{ /link } \" class=\"button\"><span class=\"icon icon16 fa-list\"></span> <span> { lang } wcf.acp.menu.link.person.list { /lang } </span></a></li> { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { include file = 'formNotice' } <form method=\"post\" action=\" { if $action == 'add' }{ link controller = 'PersonAdd' }{ /link }{ else }{ link controller = 'PersonEdit' object = $person }{ /link }{ /if } \"> <div class=\"section\"> <dl { if $errorField == 'firstName' } class=\"formError\" { /if } > <dt><label for=\"firstName\"> { lang } wcf.person.firstName { /lang } </label></dt> <dd> <input type=\"text\" id=\"firstName\" name=\"firstName\" value=\" { $firstName } \" required autofocus maxlength=\"255\" class=\"long\"> { if $errorField == 'firstName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.firstName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl { if $errorField == 'lastName' } class=\"formError\" { /if } > <dt><label for=\"lastName\"> { lang } wcf.person.lastName { /lang } </label></dt> <dd> <input type=\"text\" id=\"lastName\" name=\"lastName\" value=\" { $lastName } \" required maxlength=\"255\" class=\"long\"> { if $errorField == 'lastName' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } wcf.acp.person.lastName.error. { $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> { event name = 'dataFields' } </div> { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> { include file = 'footer' } We will now only concentrate on the new parts compared to personList.tpl : We use the $action variable to distinguish between the languages items used for adding a person and for creating a person. Including the formError template automatically shows an error message if the validation failed. The .success element is shown after successful saving the data and, again, shows different a text depending on the executed action. The main part is the form element which has a common structure you will find in many forms in WoltLab Suite Core. The notable parts here are: The action attribute of the form element is set depending on which controller will handle the request. In the link for the edit controller, we can now simply pass the edited Person object directly as the Person class implements the IRouteController interface. The field that caused the validation error can be accessed via $errorField . The type of the validation error can be accessed via $errorType . For an empty input field, we show the generic wcf.global.form.error.empty language item. In all other cases, we use the error type to determine the object- and property-specific language item to show. The approach used here allows plugins to easily add further validation error messages by simply using a different error type and providing the associated language item. Input fields can be grouped into different .section elements. At the end of each .section element, there should be an template event whose name ends with Fields . The first part of the event name should reflect the type of fields in the particular .section element. Here, the input fields are just general \u201cdata\u201d fields so that the event is called dataFields . After the last .section element, fire a section event so that plugins can add further sections. Lastly, the .formSubmit shows the submit button and {csrfToken} contains a CSRF token that is automatically validated after the form is submitted.","title":"personAdd.tpl"},{"location":"tutorial/series/part_1/#person-edit-form","text":"As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing.","title":"Person Edit Form"},{"location":"tutorial/series/part_1/#personeditform","text":"<? php namespace wcf\\acp\\form ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonAction ; use wcf\\form\\AbstractForm ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the form to edit an existing person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Acp\\Form */ class PersonEditForm extends PersonAddForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.person' ; /** * edited person object * @var Person */ public $person = null ; /** * id of the edited person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'action' => 'edit' , 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( empty ( $_POST )) { $this -> firstName = $this -> person -> firstName ; $this -> lastName = $this -> person -> lastName ; } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } /** * @inheritDoc */ public function save () { AbstractForm :: save (); $this -> objectAction = new PersonAction ([ $this -> person ], 'update' , [ 'data' => array_merge ( $this -> additionalFields , [ 'firstName' => $this -> firstName , 'lastName' => $this -> lastName ]) ]); $this -> objectAction -> executeAction (); $this -> saved (); // show success message WCF :: getTPL () -> assign ( 'success' , true ); } } In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited. After setting a different active menu item, we declare two new properties for the edited person: the id of the person passed in the URL is stored in $personID and based on this ID, a Person object is created that is stored in the $person property. Now let use go through the different methods in chronological order again: readParameters() reads the passed ID of the edited person and creates a Person object based on this ID. If the ID is invalid, $this->person->personID is null and an IllegalLinkException is thrown. readData() only executes additional code in the case if $_POST is empty, thus only for the initial request before the form has been submitted. The data properties of PersonAddForm are populated with the data of the edited person so that this data is shown in the form for the initial request. save() handles saving the changed data. !!! warning \"Do not call parent::save() because that would cause PersonAddForm::save() to be executed and thus a new person would to be created! In order for the save event to be fired, call AbstractForm::save() instead!\" The only differences compared to PersonAddForm::save() are that we pass the edited object to the PersonAction constructor, execute the update action instead of the create action and do not clear the input fields after saving the changes. 1. In assignVariables() , we assign the edited Person object to the template, which is required to create the link in the form\u2019s action property. Furthermore, we assign the template variable $action edit as value. !!! info \"After calling parent::assignVariables() , the template variable $action actually has the value add so that here, we are overwriting this already assigned value.\"","title":"PersonEditForm"},{"location":"tutorial/series/part_1/#frontend","text":"For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people. This page should also be directly linked in the main menu.","title":"Frontend"},{"location":"tutorial/series/part_1/#pagexml","text":"First, let us register the page with the system because every front end page or form needs to be explicitly registered using the page package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> </import> </data> For more information about what each of the elements means, please refer to the page package installation plugin page .","title":"page.xml"},{"location":"tutorial/series/part_1/#menuitemxml","text":"Next, we register the menu item using the menuItem package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/menuItem.xsd\" > <import> <item identifier= \"com.woltlab.wcf.people.PersonList\" > <menu> com.woltlab.wcf.MainMenu </menu> <title language= \"de\" > Personen </title> <title language= \"en\" > People </title> <page> com.woltlab.wcf.people.PersonList </page> </item> </import> </data> Here, the import parts are that we register the menu item for the main menu com.woltlab.wcf.MainMenu and link the menu item with the page com.woltlab.wcf.people.PersonList , which we just registered.","title":"menuItem.xml"},{"location":"tutorial/series/part_1/#people-list_1","text":"As in the ACP, we need a controller and a template. You might notice that both the controller\u2019s (unqualified) class name and the template name are the same for the ACP and the front end. This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories.","title":"People List"},{"location":"tutorial/series/part_1/#personlistpage_1","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\PersonList ; /** * Shows the list of people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonListPage extends SortablePage { /** * @inheritDoc */ public $defaultSortField = 'lastName' ; /** * @inheritDoc */ public $objectListClassName = PersonList :: class ; /** * @inheritDoc */ public $validSortFields = [ 'personID' , 'firstName' , 'lastName' ]; } This class is almost identical to the ACP version. In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page. Furthermore, $neededPermissions has not been set because in the front end, users do not need any special permission to access the page. In the front end, we explicitly set the $defaultSortField so that the people listed on the page are sorted by their last name (in ascending order) by default.","title":"PersonListPage"},{"location":"tutorial/series/part_1/#personlisttpl_1","text":"{ capture assign = 'contentTitle' }{ lang } wcf.person.list { /lang } <span class=\"badge\"> { # $items } </span> { /capture } { capture assign = 'headContent' } { if $pageNo < $pages } <link rel=\"next\" href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo + 1 }{ /link } \"> { /if } { if $pageNo > 1 } <link rel=\"prev\" href=\" { link controller = 'PersonList' }{ if $pageNo > 2 } pageNo= { @ $pageNo - 1 }{ /if }{ /link } \"> { /if } <link rel=\"canonical\" href=\" { link controller = 'PersonList' }{ if $pageNo > 1 } pageNo= { @ $pageNo }{ /if }{ /link } \"> { /capture } { capture assign = 'sidebarRight' } <section class=\"box\"> <form method=\"post\" action=\" { link controller = 'PersonList' }{ /link } \"> <h2 class=\"boxTitle\"> { lang } wcf.global.sorting { /lang } </h2> <div class=\"boxContent\"> <dl> <dt></dt> <dd> <select id=\"sortField\" name=\"sortField\"> <option value=\"firstName\" { if $sortField == 'firstName' } selected { /if } > { lang } wcf.person.firstName { /lang } </option> <option value=\"lastName\" { if $sortField == 'lastName' } selected { /if } > { lang } wcf.person.lastName { /lang } </option> { event name = 'sortField' } </select> <select name=\"sortOrder\"> <option value=\"ASC\" { if $sortOrder == 'ASC' } selected { /if } > { lang } wcf.global.sortOrder.ascending { /lang } </option> <option value=\"DESC\" { if $sortOrder == 'DESC' } selected { /if } > { lang } wcf.global.sortOrder.descending { /lang } </option> </select> </dd> </dl> <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> </div> </div> </form> </section> { /capture } { include file = 'header' } { hascontent } <div class=\"paginationTop\"> { content } { pages print = true assign = pagesLinks controller = 'PersonList' link = \"pageNo=%d&sortField=$sortField&sortOrder=$sortOrder\" } { /content } </div> { /hascontent } { if $items } <div class=\"section sectionContainerList\"> <ol class=\"containerList personList\"> { foreach from = $objects item = person } <li> <div class=\"box48\"> <span class=\"icon icon48 fa-user\"></span> <div class=\"details personInformation\"> <div class=\"containerHeadline\"> <h3> { $person } </h3> </div> { hascontent } <ul class=\"inlineList commaSeparated\"> { content }{ event name = 'personData' }{ /content } </ul> { /hascontent } { hascontent } <dl class=\"plain inlineDataList small\"> { content }{ event name = 'personStatistics' }{ /content } </dl> { /hascontent } </div> </div> </li> { /foreach } </ol> </div> { else } <p class=\"info\"> { lang } wcf.global.noItems { /lang } </p> { /if } <footer class=\"contentFooter\"> { hascontent } <div class=\"paginationBottom\"> { content }{ @ $pagesLinks }{ /content } </div> { /hascontent } { hascontent } <nav class=\"contentFooterNavigation\"> <ul> { content }{ event name = 'contentFooterNavigation' }{ /content } </ul> </nav> { /hascontent } </footer> { include file = 'footer' } If you compare this template to the one used in the ACP, you will recognize similar elements like the .paginationTop element, the p.info element if no people exist, and the .contentFooter element. Furthermore, we include a template called header before actually showing any of the page contents and terminate the template by including the footer template. Now, let us take a closer look at the differences: We do not explicitly create a .contentHeader element but simply assign the title to the contentTitle variable. The value of the assignment is simply the title of the page and a badge showing the number of listed people. The header template that we include later will handle correctly displaying the content header on its own based on the $contentTitle variable. Next, we create additional element for the HTML document\u2019s <head> element. In this case, we define the canonical link of the page and, because we are showing paginated content, add links to the previous and next page (if they exist). We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head. Instead, usually a box is created in the sidebar on the right-hand side that contains select elements to determine sort field and sort order. The main part of the page is the listing of the people. We use a structure similar to the one used for displaying registered users. Here, for each person, we simply display a FontAwesome icon representing a person and show the person\u2019s full name relying on Person::__toString() . Additionally, like in the user list, we provide the initially empty ul.inlineList.commaSeparated and dl.plain.inlineDataList.small elements that can be filled by plugins using the templates events.","title":"personList.tpl"},{"location":"tutorial/series/part_1/#usergroupoptionxml","text":"We have already used the admin.content.canManagePeople permissions several times, now we need to install it using the userGroupOption package installation plugin : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/userGroupOption.xsd\" > <import> <options> <option name= \"admin.content.canManagePeople\" > <categoryname> admin.content </categoryname> <optiontype> boolean </optiontype> <defaultvalue> 0 </defaultvalue> <admindefaultvalue> 1 </admindefaultvalue> <usersonly> 1 </usersonly> </option> </options> </import> </data> We use the existing admin.content user group option category for the permission as the people are \u201ccontent\u201d (similar the the ACP menu item). As the permission is for administrators only, we set defaultvalue to 0 and admindefaultvalue to 1 . This permission is only relevant for registered users so that it should not be visible when editing the guest user group. This is achieved by setting usersonly to 1 .","title":"userGroupOption.xml"},{"location":"tutorial/series/part_1/#packagexml","text":"Lastly, we need to create the package.xml file. For more information about this kind of file, please refer to the package.xml page . <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People </packagename> <packagedescription> Adds a simple management system for people as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"acpMenu\" /> <instruction type= \"page\" /> <instruction type= \"menuItem\" /> <instruction type= \"userGroupOption\" /> </instructions> </package> As this is a package for WoltLab Suite Core 3, we need to require it using <requiredpackage> . We require the latest version (when writing this tutorial) 3.0.0 RC 4 . Additionally, we disallow installation of the package in the next major version 3.1 by excluding the 3.1.0 Alpha 1 version. This ensures that if changes from WoltLab Suite Core 3.0 to 3.1 require changing some parts of the package, it will not break the instance in which the package is installed. The most important part are to installation instructions. First, we install the ACP templates, files and templates, create the database table and import the language item. Afterwards, the ACP menu items and the permission are added. Now comes the part of the instructions where the order of the instructions is crucial: In menuItem.xml , we refer to the com.woltlab.wcf.people.PersonList page that is delivered by page.xml . As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item! This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_2/","text":"Part 2: Event Listeners and Template Listeners # In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date. Package Functionality # The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end Used Components # We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events . Package Structure # The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl Extending Person Model ( install.sql ) # The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday . Setting Birthday in ACP # To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below . Adding Birthday Table Column in ACP # To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td> Adding Birthday in Front End # In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if } templateListener.xml # The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template. eventListener.xml # There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data> package.xml # The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"Part 2"},{"location":"tutorial/series/part_2/#part-2-event-listeners-and-template-listeners","text":"In the first part of this tutorial series, we have created the base structure of our people management package. In further parts, we will use the package of the first part as a basis to directly add new features. In order to explain how event listeners and template works, however, we will not directly adding a new feature to the package by altering it in this part, but we will assume that somebody else created the package and that we want to extend it the \u201ccorrect\u201d way by creating a plugin. The goal of the small plugin that will be created in this part is to add the birthday of the managed people. As in the first part, we will not bother with careful validation of the entered date but just make sure that it is a valid date.","title":"Part 2: Event Listeners and Template Listeners"},{"location":"tutorial/series/part_2/#package-functionality","text":"The package should provide the following possibilities/functions: List person\u2019s birthday (if set) in people list in the ACP Sort people list by birthday in the ACP Add or remove birthday when adding or editing person List person\u2019s birthday (if set) in people list in the front end Sort people list by birthday in the front end","title":"Package Functionality"},{"location":"tutorial/series/part_2/#used-components","text":"We will use the following package installation plugins: acpTemplate package installation plugin , eventListener package installation plugin , file package installation plugin , language package installation plugin , sql package installation plugin , template package installation plugin , templateListener package installation plugin . For more information about the event system, please refer to the dedicated page on events .","title":"Used Components"},{"location":"tutorial/series/part_2/#package-structure","text":"The package will have the following file structure: \u251c\u2500\u2500 acptemplates \u2502 \u2514\u2500\u2500 __personAddBirthday.tpl \u251c\u2500\u2500 eventListener.xml \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u2514\u2500\u2500 system \u2502 \u2514\u2500\u2500 event \u2502 \u2514\u2500\u2500 listener \u2502 \u251c\u2500\u2500 BirthdayPersonAddFormListener.class.php \u2502 \u2514\u2500\u2500 BirthdaySortFieldPersonListPageListener.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 templateListener.xml \u2514\u2500\u2500 templates \u251c\u2500\u2500 __personListBirthday.tpl \u2514\u2500\u2500 __personListBirthdaySortField.tpl","title":"Package Structure"},{"location":"tutorial/series/part_2/#extending-person-model-installsql","text":"The existing model of a person only contains the person\u2019s first name and their last name (in additional to the id used to identify created people). To add the birthday to the model, we need to create an additional database table column using the sql package installation plugin : ALTER TABLE wcf1_person ADD birthday DATE NOT NULL ; If we have a Person object , this new property can be accessed the same way as the personID property, the firstName property, or the lastName property from the base package: $person->birthday .","title":"Extending Person Model (install.sql)"},{"location":"tutorial/series/part_2/#setting-birthday-in-acp","text":"To set the birthday of a person, we need to extend the personAdd template to add an additional birthday field. This can be achieved using the dataFields template event at whose position we inject the following template code: < dl { if $ errorField == 'birthday' } class = \"formError\" { / if } > < dt >< label for = \"birthday\" > { lang } wcf . person . birthday { / lang } </ label ></ dt > < dd > < input type = \"date\" id = \"birthday\" name = \"birthday\" value = \"{$birthday}\" > { if $ errorField == 'birthday' } < small class = \"innerError\" > { if $ errorType == 'noValidSelection' } { lang } wcf . global . form . error . noValidSelection { / lang } { else } { lang } wcf . acp . person . birthday . error . {$ errorType }{ / lang } { / if } </ small > { / if } </ dd > </ dl > which we store in a __personAddBirthday.tpl template file. The used language item wcf.person.birthday is actually the only new one for this package: <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"de\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Geburtstag ]] ></ item > </ category > </ language > <? xml version = \"1.0\" encoding = \"UTF-8\" ?> < language xmlns = \"http://www.woltlab.com\" xmlns : xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi : schemaLocation = \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/language.xsd\" languagecode = \"en\" > < category name = \"wcf.person\" > < item name = \"wcf.person.birthday\" ><! [ CDATA [ Birthday ]] ></ item > </ category > </ language > The template listener needs to be registered using the templateListener package installation plugin . The corresponding complete templateListener.xml file is included below . The template code alone is not sufficient because the birthday field is, at the moment, neither read, nor processed, nor saved by any PHP code. This can be be achieved, however, by adding event listeners to PersonAddForm and PersonEditForm which allow us to execute further code at specific location of the program. Before we take a look at the event listener code, we need to identify exactly which additional steps we need to undertake: If a person is edited and the form has not been submitted, the existing birthday of that person needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be read. If a person is added or edited and the form has been submitted, the new birthday value needs to be validated. If a person is added or edited and the new birthday value has been successfully validated, the new birthday value needs to be saved. If a person is added and the new birthday value has been successfully saved, the internally stored birthday needs to be reset so that the birthday field is empty when the form is shown again. The internally stored birthday value needs to be assigned to the template. The following event listeners achieves these requirements: <? php namespace wcf\\system\\event\\listener ; use wcf\\acp\\form\\PersonAddForm ; use wcf\\acp\\form\\PersonEditForm ; use wcf\\form\\IForm ; use wcf\\page\\IPage ; use wcf\\system\\exception\\UserInputException ; use wcf\\system\\WCF ; use wcf\\util\\StringUtil ; /** * Handles setting the birthday when adding and editing people. * * @author Matthias Schmidt * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdayPersonAddFormListener extends AbstractEventListener { /** * birthday of the created or edited person * @var string */ protected $birthday = '' ; /** * @see IPage::assignVariables() */ protected function onAssignVariables () { WCF :: getTPL () -> assign ( 'birthday' , $this -> birthday ); } /** * @see IPage::readData() */ protected function onReadData ( PersonEditForm $form ) { if ( empty ( $_POST )) { $this -> birthday = $form -> person -> birthday ; if ( $this -> birthday === '0000-00-00' ) { $this -> birthday = '' ; } } } /** * @see IForm::readFormParameters() */ protected function onReadFormParameters () { if ( isset ( $_POST [ 'birthday' ])) { $this -> birthday = StringUtil :: trim ( $_POST [ 'birthday' ]); } } /** * @see IForm::save() */ protected function onSave ( PersonAddForm $form ) { if ( $this -> birthday ) { $form -> additionalFields [ 'birthday' ] = $this -> birthday ; } else { $form -> additionalFields [ 'birthday' ] = '0000-00-00' ; } } /** * @see IForm::saved() */ protected function onSaved () { $this -> birthday = '' ; } /** * @see IForm::validate() */ protected function onValidate () { if ( empty ( $this -> birthday )) { return ; } if ( ! preg_match ( '/^(\\d{4})-(\\d{2})-(\\d{2})$/' , $this -> birthday , $match )) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } if ( ! checkdate ( intval ( $match [ 2 ]), intval ( $match [ 3 ]), intval ( $match [ 1 ]))) { throw new UserInputException ( 'birthday' , 'noValidSelection' ); } } } Some notes on the code: We are inheriting from AbstractEventListener , instead of just implementing the IParameterizedEventListener interface. The execute() method of AbstractEventListener contains a dispatcher that automatically calls methods called on followed by the event name with the first character uppercased, passing the event object and the $parameters array. This simple pattern results in the event foo being forwarded to the method onFoo($eventObj, $parameters) . The birthday column has a default value of 0000-00-00 , which we interpret as \u201cbirthday not set\u201d. To show an empty input field in this case, we empty the birthday property after reading such a value in readData() . The validation of the date is, as mentioned before, very basic and just checks the form of the string and uses PHP\u2019s checkdate function to validate the components. The save needs to make sure that the passed date is actually a valid date and set it to 0000-00-00 if no birthday is given. To actually save the birthday in the database, we do not directly manipulate the database but can add an additional field to the data array passed to PersonAction::create() via AbstractForm::$additionalFields . As the save event is the last event fired before the actual save process happens, this is the perfect event to set this array element. The event listeners are installed using the eventListener.xml file shown below .","title":"Setting Birthday in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-table-column-in-acp","text":"To add a birthday column to the person list page in the ACP, we need three parts: an event listener that makes the birthday database table column a valid sort field, a template listener that adds the birthday column to the table\u2019s head, and a template listener that adds the birthday column to the table\u2019s rows. The first part is a very simple class: <? php namespace wcf\\system\\event\\listener ; use wcf\\page\\SortablePage ; /** * Makes people's birthday a valid sort field in the ACP and the front end. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Event\\Listener */ class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { /** * @inheritDoc */ public function execute ( $eventObj , $className , $eventName , array & $parameters ) { /** @var SortablePage $eventObj */ $eventObj -> validSortFields [] = 'birthday' ; } } We use SortablePage as a type hint instead of wcf\\acp\\page\\PersonListPage because we will be using the same event listener class in the front end to also allow sorting that list by birthday. As the relevant template codes are only one line each, we will simply put them directly in the templateListener.xml file that will be shown later on . The code for the table head is similar to the other th elements: <th class=\"columnDate columnBirthday { if $sortField == 'birthday' } active { @ $sortOrder }{ /if } \"><a href=\" { link controller = 'PersonList' } pageNo= { @ $pageNo } &sortField=birthday&sortOrder= { if $sortField == 'birthday' && $sortOrder == 'ASC' } DESC { else } ASC { /if }{ /link } \"> { lang } wcf.person.birthday { /lang } </a></th> For the table body\u2019s column, we need to make sure that the birthday is only show if it is actually set: <td class=\"columnDate columnBirthday\"> { if $person -> birthday !== '0000-00-00' }{ @ $person -> birthday | strtotime | date }{ /if } </td>","title":"Adding Birthday Table Column in ACP"},{"location":"tutorial/series/part_2/#adding-birthday-in-front-end","text":"In the front end, we also want to make the list sortable by birthday and show the birthday as part of each person\u2019s \u201cstatistics\u201d. To add the birthday as a valid sort field, we use BirthdaySortFieldPersonListPageListener just as in the ACP. In the front end, we will now use a template ( __personListBirthdaySortField.tpl ) instead of a directly putting the template code in the templateListener.xml file: <option value=\"birthday\" { if $sortField == 'birthday' } selected { /if } > { lang } wcf.person.birthday { /lang } </option> You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use. Putting the template code into a file has the advantage that in the administrator is able to edit the code directly via a custom template group, even though in this case this might not be very probable. To show the birthday, we use the following template code for the personStatistics template event, which again makes sure that the birthday is only shown if it is actually set: { if $person -> birthday !== '0000-00-00' } <dt> { lang } wcf.person.birthday { /lang } </dt> <dd> { @ $person -> birthday | strtotime | date } </dd> { /if }","title":"Adding Birthday in Front End"},{"location":"tutorial/series/part_2/#templatelistenerxml","text":"The following code shows the templateListener.xml file used to install all mentioned template listeners: <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/tornado/XSD/templateListener.xsd\" > <import> <!-- admin --> <templatelistener name= \"personListBirthdayColumnHead\" > <eventname> columnHeads </eventname> <environment> admin </environment> <templatecode> <![CDATA[<th class=\"columnDate columnBirthday{if $sortField == 'birthday'} active {@$sortOrder}{/if}\"><a href=\"{link controller='PersonList'}pageNo={@$pageNo}&sortField=birthday&sortOrder={if $sortField == 'birthday' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}\">{lang}wcf.person.birthday{/lang}</a></th>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdayColumn\" > <eventname> columns </eventname> <environment> admin </environment> <templatecode> <![CDATA[<td class=\"columnDate columnBirthday\">{if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}</td>]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personAddBirthday\" > <eventname> dataFields </eventname> <environment> admin </environment> <templatecode> <![CDATA[{include file='__personAddBirthday'}]]> </templatecode> <templatename> personAdd </templatename> </templatelistener> <!-- /admin --> <!-- user --> <templatelistener name= \"personListBirthday\" > <eventname> personStatistics </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthday'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <templatelistener name= \"personListBirthdaySortField\" > <eventname> sortField </eventname> <environment> user </environment> <templatecode> <![CDATA[{include file='__personListBirthdaySortField'}]]> </templatecode> <templatename> personList </templatename> </templatelistener> <!-- /user --> </import> </data> In cases where a template is used, we simply use the include syntax to load the template.","title":"templateListener.xml"},{"location":"tutorial/series/part_2/#eventlistenerxml","text":"There are two event listeners, birthdaySortFieldAdminPersonList and birthdaySortFieldPersonList , that make birthday a valid sort field in the ACP and the front end, respectively, and the rest takes care of setting the birthday. The event listener birthdayPersonAddFormInherited takes care of the events that are relevant for both adding and editing people, thus it listens to the PersonAddForm class but has inherit set to 1 so that it also listens to the events of the PersonEditForm class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener birthdayPersonEditForm only listens to that class. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/eventListener.xsd\" > <import> <!-- admin --> <eventlistener name= \"birthdaySortFieldAdminPersonList\" > <environment> admin </environment> <eventclassname> wcf\\acp\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> saved </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <eventlistener name= \"birthdayPersonAddFormInherited\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonAddForm </eventclassname> <eventname> assignVariables,readFormParameters,save,validate </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> <inherit> 1 </inherit> </eventlistener> <eventlistener name= \"birthdayPersonEditForm\" > <environment> admin </environment> <eventclassname> wcf\\acp\\form\\PersonEditForm </eventclassname> <eventname> readData </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdayPersonAddFormListener </listenerclassname> </eventlistener> <!-- /admin --> <!-- user --> <eventlistener name= \"birthdaySortFieldPersonList\" > <environment> user </environment> <eventclassname> wcf\\page\\PersonListPage </eventclassname> <eventname> validateSortField </eventname> <listenerclassname> wcf\\system\\event\\listener\\BirthdaySortFieldPersonListPageListener </listenerclassname> </eventlistener> <!-- /user --> </import> </data>","title":"eventListener.xml"},{"location":"tutorial/series/part_2/#packagexml","text":"The only relevant difference between the package.xml file of the base page from part 1 and the package.xml file of this package is that this package requires the base package com.woltlab.wcf.people (see <requiredpackages> ): <?xml version=\"1.0\" encoding=\"UTF-8\"?> <package name= \"com.woltlab.wcf.people.birthday\" xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/package.xsd\" > <packageinformation> <packagename> WoltLab Suite Core Tutorial: People (Birthday) </packagename> <packagedescription> Adds a birthday field to the people management system as part of a tutorial to create packages. </packagedescription> <version> 3.1.0 </version> <date> 2018-03-30 </date> </packageinformation> <authorinformation> <author> WoltLab GmbH </author> <authorurl> http://www.woltlab.com </authorurl> </authorinformation> <requiredpackages> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf </requiredpackage> <requiredpackage minversion= \"3.1.0\" > com.woltlab.wcf.people </requiredpackage> </requiredpackages> <excludedpackages> <excludedpackage version= \"3.2.0 Alpha 1\" > com.woltlab.wcf </excludedpackage> </excludedpackages> <compatibility> <api version= \"2018\" /> </compatibility> <instructions type= \"install\" > <instruction type= \"acpTemplate\" /> <instruction type= \"file\" /> <instruction type= \"sql\" /> <instruction type= \"template\" /> <instruction type= \"language\" /> <instruction type= \"eventListener\" /> <instruction type= \"templateListener\" /> </instructions> </package> This concludes the second part of our tutorial series after which you now have extended the base package using event listeners and template listeners that allow you to enter the birthday of the people. The complete source code of this part can be found on GitHub .","title":"package.xml"},{"location":"tutorial/series/part_3/","text":"Tutorial Series Part 3: Person Page and Comments # In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality. Package Functionality # In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages Used Components # In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler. Package Structure # The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub . Runtime Cache # To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; } Comments # To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments. Person Page # PersonPage # <? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments. person.tpl # {capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix. page.xml # <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent. PersonPageHandler # <? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"Part 3"},{"location":"tutorial/series/part_3/#tutorial-series-part-3-person-page-and-comments","text":"In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details. To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite\u2019s reusable comment functionality.","title":"Tutorial Series Part 3: Person Page and Comments"},{"location":"tutorial/series/part_3/#package-functionality","text":"In addition to the existing functions from part 1 , the package will provide the following possibilities/functions after this part of the tutorial: Details page for each person linked in the front end person list Comment on people on their respective page (can be disabled per person) User online location for person details page with name and link to person details page Create menu items linking to specific person details pages","title":"Package Functionality"},{"location":"tutorial/series/part_3/#used-components","text":"In addition to the components used in part 1 , we will use the objectType package installation plugin , use the comment API , create a runtime cache , and create a page handler.","title":"Used Components"},{"location":"tutorial/series/part_3/#package-structure","text":"The complete package will have the following file structure (including the files from part 1 ): \u251c\u2500\u2500 acpMenu.xml \u251c\u2500\u2500 acptemplates \u2502 \u251c\u2500\u2500 personAdd.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u251c\u2500\u2500 files \u2502 \u2514\u2500\u2500 lib \u2502 \u251c\u2500\u2500 acp \u2502 \u2502 \u251c\u2500\u2500 form \u2502 \u2502 \u2502 \u251c\u2500\u2500 PersonAddForm.class.php \u2502 \u2502 \u2502 \u2514\u2500\u2500 PersonEditForm.class.php \u2502 \u2502 \u2514\u2500\u2500 page \u2502 \u2502 \u2514\u2500\u2500 PersonListPage.class.php \u2502 \u251c\u2500\u2500 data \u2502 \u2502 \u2514\u2500\u2500 person \u2502 \u2502 \u251c\u2500\u2500 Person.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonAction.class.php \u2502 \u2502 \u251c\u2500\u2500 PersonEditor.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonList.class.php \u2502 \u251c\u2500\u2500 page \u2502 \u2502 \u251c\u2500\u2500 PersonListPage.class.php \u2502 \u2502 \u2514\u2500\u2500 PersonPage.class.php \u2502 \u2514\u2500\u2500 system \u2502 \u251c\u2500\u2500 cache \u2502 \u2502 \u2514\u2500\u2500 runtime \u2502 \u2502 \u2514\u2500\u2500 PersonRuntimeCache.class.php \u2502 \u251c\u2500\u2500 comment \u2502 \u2502 \u2514\u2500\u2500 manager \u2502 \u2502 \u2514\u2500\u2500 PersonCommentManager.class.php \u2502 \u2514\u2500\u2500 page \u2502 \u2514\u2500\u2500 handler \u2502 \u2514\u2500\u2500 PersonPageHandler.class.php \u251c\u2500\u2500 install.sql \u251c\u2500\u2500 language \u2502 \u251c\u2500\u2500 de.xml \u2502 \u2514\u2500\u2500 en.xml \u251c\u2500\u2500 menuItem.xml \u251c\u2500\u2500 objectType.xml \u251c\u2500\u2500 package.xml \u251c\u2500\u2500 page.xml \u251c\u2500\u2500 templates \u2502 \u251c\u2500\u2500 person.tpl \u2502 \u2514\u2500\u2500 personList.tpl \u2514\u2500\u2500 userGroupOption.xml We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub .","title":"Package Structure"},{"location":"tutorial/series/part_3/#runtime-cache","text":"To reduce the number of database queries when different APIs require person objects, we implement a runtime cache for people: <? php namespace wcf\\system\\cache\\runtime ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonList ; /** * Runtime cache implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Cache\\Runtime * @since 3.0 * * @method Person[] getCachedObjects() * @method Person getObject($objectID) * @method Person[] getObjects(array $objectIDs) */ class PersonRuntimeCache extends AbstractRuntimeCache { /** * @inheritDoc */ protected $listClassName = PersonList :: class ; }","title":"Runtime Cache"},{"location":"tutorial/series/part_3/#comments","text":"To allow users to comment on people, we need to tell the system that people support comments. This is done by registering a com.woltlab.wcf.comment.commentableContent object type whose processor implements ICommentManager : <?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/objectType.xsd\" > <import> <type> <name> com.woltlab.wcf.person.personComment </name> <definitionname> com.woltlab.wcf.comment.commentableContent </definitionname> <classname> wcf\\system\\comment\\manager\\PersonCommentManager </classname> </type> </import> </data> The PersonCommentManager class extended ICommentManager \u2019s default implementation AbstractCommentManager : <? php namespace wcf\\system\\comment\\manager ; use wcf\\data\\person\\Person ; use wcf\\data\\person\\PersonEditor ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\WCF ; /** * Comment manager implementation for people. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Comment\\Manager */ class PersonCommentManager extends AbstractCommentManager { /** * @inheritDoc */ protected $permissionAdd = 'user.person.canAddComment' ; /** * @inheritDoc */ protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration' ; /** * @inheritDoc */ protected $permissionCanModerate = 'mod.person.canModerateComment' ; /** * @inheritDoc */ protected $permissionDelete = 'user.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionEdit = 'user.person.canEditComment' ; /** * @inheritDoc */ protected $permissionModDelete = 'mod.person.canDeleteComment' ; /** * @inheritDoc */ protected $permissionModEdit = 'mod.person.canEditComment' ; /** * @inheritDoc */ public function getLink ( $objectTypeID , $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * @inheritDoc */ public function isAccessible ( $objectID , $validateWritePermission = false ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function getTitle ( $objectTypeID , $objectID , $isResponse = false ) { if ( $isResponse ) { return WCF :: getLanguage () -> get ( 'wcf.person.commentResponse' ); } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.person.comment' ); } /** * @inheritDoc */ public function updateCounter ( $objectID , $value ) { ( new PersonEditor ( new Person ( $objectID ))) -> updateCounters ([ 'comments' => $value ]); } } First, the system is told the names of the permissions via the $permission* properties. More information about comment permissions can be found here . The getLink() method returns the link to the person with the passed comment id. As in isAccessible() , PersonRuntimeCache is used to potentially save database queries. The isAccessible() method checks if the active user can access the relevant person. As we do not have any special restrictions for accessing people, we only need to check if the person exists. The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case. The updateCounter() updates the comments\u2019 counter of the person. We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments. Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP. With this option, comments on individual people can be disabled. Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments.","title":"Comments"},{"location":"tutorial/series/part_3/#person-page","text":"","title":"Person Page"},{"location":"tutorial/series/part_3/#personpage","text":"<? php namespace wcf\\page ; use wcf\\data\\person\\Person ; use wcf\\system\\comment\\CommentHandler ; use wcf\\system\\comment\\manager\\PersonCommentManager ; use wcf\\system\\exception\\IllegalLinkException ; use wcf\\system\\WCF ; /** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\Page */ class PersonPage extends AbstractPage { /** * list of comments * @var StructuredCommentList */ public $commentList ; /** * person comment manager object * @var PersonCommentManager */ public $commentManager ; /** * id of the person comment object type * @var integer */ public $commentObjectTypeID = 0 ; /** * shown person * @var Person */ public $person ; /** * id of the shown person * @var integer */ public $personID = 0 ; /** * @inheritDoc */ public function assignVariables () { parent :: assignVariables (); WCF :: getTPL () -> assign ([ 'commentCanAdd' => WCF :: getSession () -> getPermission ( 'user.person.canAddComment' ), 'commentList' => $this -> commentList , 'commentObjectTypeID' => $this -> commentObjectTypeID , 'lastCommentTime' => $this -> commentList ? $this -> commentList -> getMinCommentTime () : 0 , 'likeData' => ( MODULE_LIKE && $this -> commentList ) ? $this -> commentList -> getLikeData () : [], 'person' => $this -> person ]); } /** * @inheritDoc */ public function readData () { parent :: readData (); if ( $this -> person -> enableComments ) { $this -> commentObjectTypeID = CommentHandler :: getInstance () -> getObjectTypeID ( 'com.woltlab.wcf.person.personComment' ); $this -> commentManager = CommentHandler :: getInstance () -> getObjectType ( $this -> commentObjectTypeID ) -> getProcessor (); $this -> commentList = CommentHandler :: getInstance () -> getCommentList ( $this -> commentManager , $this -> commentObjectTypeID , $this -> person -> personID ); } } /** * @inheritDoc */ public function readParameters () { parent :: readParameters (); if ( isset ( $_REQUEST [ 'id' ])) $this -> personID = intval ( $_REQUEST [ 'id' ]); $this -> person = new Person ( $this -> personID ); if ( ! $this -> person -> personID ) { throw new IllegalLinkException (); } } } The PersonPage class is similar to the PersonEditForm in the ACP in that it reads the id of the requested person from the request data and validates the id in readParameters() . The rest of the code only handles fetching the list of comments on the requested person. In readData() , this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person. The assignVariables() method assigns some additional template variables like $commentCanAdd , which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime , which contains the UNIX timestamp of the last comment, and $likeData , which contains data related to the likes for the disabled comments.","title":"PersonPage"},{"location":"tutorial/series/part_3/#persontpl","text":"{capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} {capture assign='contentTitle'}{$person}{/capture} {include file='header'} {if $person->enableComments} {if $commentList|count || $commentCanAdd} <section id=\"comments\" class=\"section sectionContainerList\"> <header class=\"sectionHeader\"> <h2 class=\"sectionTitle\">{lang}wcf.person.comments{/lang}{if $person->comments} <span class=\"badge\">{#$person->comments}</span>{/if}</h2> </header> {include file='__commentJavaScript' commentContainerID='personCommentList'} <div class=\"personComments\"> <ul id=\"personCommentList\" class=\"commentList containerList\" data-can-add=\"{if $commentCanAdd}true{else}false{/if}\" data-object-id=\"{@$person->personID}\" data-object-type-id=\"{@$commentObjectTypeID}\" data-comments=\"{if $person->comments}{@$commentList->countObjects()}{else}0{/if}\" data-last-comment-time=\"{@$lastCommentTime}\" > {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} {include file='commentList'} </ul> </div> </section> {/if} {/if} <footer class=\"contentFooter\"> {hascontent} <nav class=\"contentFooterNavigation\"> <ul> {content}{event name='contentFooterNavigation'}{/content} </ul> </nav> {/hascontent} </footer> {include file='footer'} For now, the person template is still very empty and only shows the comments in the content area. The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container ul#personCommentList element for the comments shown by commentList template. The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones. The commentListAddComment template adds the WYSIWYG support. The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix.","title":"person.tpl"},{"location":"tutorial/series/part_3/#pagexml","text":"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <data xmlns= \"http://www.woltlab.com\" xmlns:xsi= \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"http://www.woltlab.com http://www.woltlab.com/XSD/tornado/page.xsd\" > <import> <page identifier= \"com.woltlab.wcf.people.PersonList\" > <pageType> system </pageType> <controller> wcf\\page\\PersonListPage </controller> <name language= \"de\" > Personen-Liste </name> <name language= \"en\" > Person List </name> <content language= \"de\" > <title> Personen </title> </content> <content language= \"en\" > <title> People </title> </content> </page> <page identifier= \"com.woltlab.wcf.people.Person\" > <pageType> system </pageType> <controller> wcf\\page\\PersonPage </controller> <handler> wcf\\system\\page\\handler\\PersonPageHandler </handler> <name language= \"de\" > Person </name> <name language= \"en\" > Person </name> <requireObjectID> 1 </requireObjectID> <parent> com.woltlab.wcf.people.PersonList </parent> </page> </import> </data> The page.xml file has been extended for the new person page with identifier com.woltlab.wcf.people.Person . Compared to the pre-existing com.woltlab.wcf.people.PersonList page, there are four differences: It has a <handler> element with a class name as value. This aspect will be discussed in more detail in the next section. There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template. The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id. This page has a <parent> page, the person list page. In general, the details page for any type of object that is listed on a different page has the list page as its parent.","title":"page.xml"},{"location":"tutorial/series/part_3/#personpagehandler","text":"<? php namespace wcf\\system\\page\\handler ; use wcf\\data\\page\\Page ; use wcf\\data\\person\\PersonList ; use wcf\\data\\user\\online\\UserOnline ; use wcf\\system\\cache\\runtime\\PersonRuntimeCache ; use wcf\\system\\database\\util\\PreparedStatementConditionBuilder ; use wcf\\system\\WCF ; /** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\\Core\\System\\Page\\Handler */ class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { use TOnlineLocationPageHandler ; /** * @inheritDoc */ public function getLink ( $objectID ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) -> getLink (); } /** * Returns the textual description if a user is currently online viewing this page. * * @see IOnlineLocationPageHandler::getOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data * @return string */ public function getOnlineLocation ( Page $page , UserOnline $user ) { if ( $user -> pageObjectID === null ) { return '' ; } $person = PersonRuntimeCache :: getInstance () -> getObject ( $user -> pageObjectID ); if ( $person === null ) { return '' ; } return WCF :: getLanguage () -> getDynamicVariable ( 'wcf.page.onlineLocation.' . $page -> identifier , [ 'person' => $person ]); } /** * @inheritDoc */ public function isValid ( $objectID = null ) { return PersonRuntimeCache :: getInstance () -> getObject ( $objectID ) !== null ; } /** * @inheritDoc */ public function lookup ( $searchString ) { $conditionBuilder = new PreparedStatementConditionBuilder ( false , 'OR' ); $conditionBuilder -> add ( 'person.firstName LIKE ?' , [ '%' . $searchString . '%' ]); $conditionBuilder -> add ( 'person.lastName LIKE ?' , [ '%' . $searchString . '%' ]); $personList = new PersonList (); $personList -> getConditionBuilder () -> add ( $conditionBuilder , $conditionBuilder -> getParameters ()); $personList -> readObjects (); $results = []; foreach ( $personList as $person ) { $results [] = [ 'image' => 'fa-user' , 'link' => $person -> getLink (), 'objectID' => $person -> personID , 'title' => $person -> getTitle () ]; } return $results ; } /** * Prepares fetching all necessary data for the textual description if a user is currently online * viewing this page. * * @see IOnlineLocationPageHandler::prepareOnlineLocation() * * @param Page $page visited page * @param UserOnline $user user online object with request data */ public function prepareOnlineLocation ( /** @noinspection PhpUnusedParameterInspection */ Page $page , UserOnline $user ) { if ( $user -> pageObjectID !== null ) { PersonRuntimeCache :: getInstance () -> cacheObjectID ( $user -> pageObjectID ); } } } Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class. As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class. For the ILookupPageHandler interface, we need to implement three methods: getLink($objectID) returns the link to the person page with the given id. In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject() . isValid($objectID) returns true if the person with the given id exists, otherwise false . Here, we use PersonRuntimeCache::getObject() again and check if the return value is null , which is the case for non-existing people. lookup($searchString) is used when setting up an internal link and when searching for the linked person. This method simply searches the first and last name of the people and returns an array with the person data. While the link , the objectID , and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa- ). Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users. To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used. The IOnlineLocationPageHandler interface requires two methods to be implemented: getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location. The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier} . prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls. In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects. This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person. The complete source code of this part can be found on GitHub .","title":"PersonPageHandler"},{"location":"view/css/","text":"CSS # SCSS and CSS # SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code. File Location # Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss . Variables # You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} . Linking images # Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash. Media Breakpoints # Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } } Available Breakpoints # Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px) Asset Preloading # WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"CSS"},{"location":"view/css/#css","text":"","title":"CSS"},{"location":"view/css/#scss-and-css","text":"SCSS is a scripting language that features a syntax similar to CSS and compiles into native CSS at runtime. It provides many great additions to CSS such as declaration nesting and variables, it is recommended to read the official guide to learn more. You can create .scss files containing only pure CSS code and it will work just fine, you are at no point required to write actual SCSS code.","title":"SCSS and CSS"},{"location":"view/css/#file-location","text":"Please place your style files in a subdirectory of the style/ directory of the target application or the Core's style directory, for example style/layout/pageHeader.scss .","title":"File Location"},{"location":"view/css/#variables","text":"You can access variables with $myVariable , variable interpolation (variables inside strings) is accomplished with #{$myVariable} .","title":"Variables"},{"location":"view/css/#linking-images","text":"Images used within a style must be located in the style's image folder. To get the folder name within the CSS the SCSS variable #{$style_image_path} can be used. The value will contain a trailing slash.","title":"Linking images"},{"location":"view/css/#media-breakpoints","text":"Media breakpoints instruct the browser to apply different CSS depending on the viewport dimensions, e.g. serving a desktop PC a different view than when viewed on a smartphone. /* red background color for desktop pc */ @include screen-lg { body { background-color : red ; } } /* green background color on smartphones and tablets */ @include screen-md-down { body { background-color : green ; } }","title":"Media Breakpoints"},{"location":"view/css/#available-breakpoints","text":"Some very large smartphones, for example the Apple iPhone 7 Plus, do match the media query for Tablets (portrait) when viewed in landscape mode. Name Devices @media equivalent screen-xs Smartphones only (max-width: 544px) screen-sm Tablets (portrait) (min-width: 545px) and (max-width: 768px) screen-sm-down Tablets (portrait) and smartphones (max-width: 768px) screen-sm-up Tablets and desktop PC (min-width: 545px) screen-sm-md Tablets only (min-width: 545px) and (max-width: 1024px) screen-md Tablets (landscape) (min-width: 769px) and (max-width: 1024px) screen-md-down Smartphones and Tablets (max-width: 1024px) screen-md-up Tablets (landscape) and desktop PC (min-width: 769px) screen-lg Desktop PC (min-width: 1025px)","title":"Available Breakpoints"},{"location":"view/css/#asset-preloading","text":"WoltLab Suite\u2019s SCSS compiler supports adding preloading metadata to the CSS. To communicate the preloading intent to the compiler, the --woltlab-suite-preload CSS variable is set to the result of the preload() function: .fooBar { --woltlab-suite-preload : # { preload ( ' #{ $style_image_path } custom/background.png' , $ as : \"image\" , $ crossorigin : false , $ type : \"image/png\" ) } ; background : url ( ' #{ $style_image_path } custom/background.png' ); } The parameters of the preload() function map directly to the preloading properties that are used within the <link> tag and the link: HTTP response header. The above example will result in a <link> similar to the following being added to the generated HTML: <link rel=\"preload\" href=\"https://example.com/images/style-1/custom/background.png\" as=\"image\" type=\"image/png\"> Use preloading sparingly for the most important resources where you can be certain that the browser will need them. Unused preloaded resources will unnecessarily waste bandwidth.","title":"Asset Preloading"},{"location":"view/languages-naming-conventions/","text":"Language Naming Conventions # This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments Forms # Fields # If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description . Error Texts # If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items. Naming Conventions # If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type. Confirmation messages # If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items. Type-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen? English # Do you really want delete the {element type}? Example: Do you really want delete the icon? Object-Specific Deletion Confirmation Message # German # {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen? English # Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>? User Group Options # Comments # German # group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren English # group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#language-naming-conventions","text":"This page contains general rules for naming language items and for their values. API-specific rules are listed on the relevant API page: Comments","title":"Language Naming Conventions"},{"location":"view/languages-naming-conventions/#forms","text":"","title":"Forms"},{"location":"view/languages-naming-conventions/#fields","text":"If you have an application foo and a database object foo\\data\\bar\\Bar with a property baz that can be set via a form field, the name of the corresponding language item has to be foo.bar.baz . If you want to add an additional description below the field, use the language item foo.bar.baz.description .","title":"Fields"},{"location":"view/languages-naming-conventions/#error-texts","text":"If an error of type {error type} for the previously mentioned form field occurs during validation, you have to use the language item foo.bar.baz.error.{error type} for the language item describing the error. Exception to this rule: There are several general error messages like wcf.global.form.error.empty that have to be used for general errors like an empty field that may not be empty to avoid duplication of the same error message text over and over again in different language items.","title":"Error Texts"},{"location":"view/languages-naming-conventions/#naming-conventions","text":"If the entered text does not conform to some special rules, i.e. if the text is invalid, use invalid as error type. If the entered text is required to be unique but is already used for another object, use notUnique as error type.","title":"Naming Conventions"},{"location":"view/languages-naming-conventions/#confirmation-messages","text":"If the language item for an action is foo.bar.action , the language item for the confirmation message has to be foo.bar.action.confirmMessage instead of foo.bar.action.sure which is still used by some older language items.","title":"Confirmation messages"},{"location":"view/languages-naming-conventions/#type-specific-deletion-confirmation-message","text":"","title":"Type-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Icon wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english","text":"Do you really want delete the {element type}? Example: Do you really want delete the icon?","title":"English"},{"location":"view/languages-naming-conventions/#object-specific-deletion-confirmation-message","text":"","title":"Object-Specific Deletion Confirmation Message"},{"location":"view/languages-naming-conventions/#german_1","text":"{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {element type} <span class=\"confirmationObject\">{object name}</span> wirklich l\u00f6schen? Example: {if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class=\"confirmationObject\">{$article->getTitle()}</span> wirklich l\u00f6schen?","title":"German"},{"location":"view/languages-naming-conventions/#english_1","text":"Do you really want to delete the {element type} <span class=\"confirmationObject\">{object name}</span>? Example: Do you really want to delete the article <span class=\"confirmationObject\">{$article->getTitle()}</span>?","title":"English"},{"location":"view/languages-naming-conventions/#user-group-options","text":"","title":"User Group Options"},{"location":"view/languages-naming-conventions/#comments","text":"","title":"Comments"},{"location":"view/languages-naming-conventions/#german_2","text":"group type action example permission name language item user adding user.foo.canAddComment Kann Kommentare erstellen user deleting user.foo.canDeleteComment Kann eigene Kommentare l\u00f6schen user editing user.foo.canEditComment Kann eigene Kommentare bearbeiten moderator deleting mod.foo.canDeleteComment Kann Kommentare l\u00f6schen moderator editing mod.foo.canEditComment Kann Kommentare bearbeiten moderator moderating mod.foo.canModerateComment Kann Kommentare moderieren","title":"German"},{"location":"view/languages-naming-conventions/#english_2","text":"group type action example permission name language item user adding user.foo.canAddComment Can create comments user deleting user.foo.canDeleteComment Can delete their comments user editing user.foo.canEditComment Can edit their comments moderator deleting mod.foo.canDeleteComment Can delete comments moderator editing mod.foo.canEditComment Can edit comments moderator moderating mod.foo.canModerateComment Can moderate comments","title":"English"},{"location":"view/languages/","text":"Languages # WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items . Special Phrases # wcf.date.dateFormat # Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation. wcf.date.timeFormat # Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters. wcf.date.firstDayOfTheWeek # Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday wcf.global.pageDirection - RTL support # Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"Languages"},{"location":"view/languages/#languages","text":"WoltLab Suite offers full i18n support with its integrated language system, including but not limited to dynamic phrases using template scripting and the built-in support for right-to-left languages. Phrases are deployed using the language package installation plugin, please also read the naming conventions for language items .","title":"Languages"},{"location":"view/languages/#special-phrases","text":"","title":"Special Phrases"},{"location":"view/languages/#wcfdatedateformat","text":"Many characters in the format have a special meaning and will be replaced with date fragments. If you want to include a literal character, you'll have to use the backslash \\ as an escape sequence to indicate that the character should be output as-is rather than being replaced. For example, Y-m-d will be output as 2018-03-30 , but \\Y-m-d will result in Y-03-30 . Defaults to M jS Y . The date format without time using PHP's format characters for the date() function. This value is also used inside the JavaScript implementation, where the characters are mapped to an equivalent representation.","title":"wcf.date.dateFormat"},{"location":"view/languages/#wcfdatetimeformat","text":"Defaults to g:i a . The date format that is used to represent a time, but not a date. Please see the explanation on wcf.date.dateFormat to learn more about the format characters.","title":"wcf.date.timeFormat"},{"location":"view/languages/#wcfdatefirstdayoftheweek","text":"Defaults to 0 . Sets the first day of the week: * 0 - Sunday * 1 - Monday","title":"wcf.date.firstDayOfTheWeek"},{"location":"view/languages/#wcfglobalpagedirection-rtl-support","text":"Defaults to ltr . Changing this value to rtl will reverse the page direction and enable the right-to-left support for phrases. Additionally, a special version of the stylesheet is loaded that contains all necessary adjustments for the reverse direction.","title":"wcf.global.pageDirection - RTL support"},{"location":"view/template-plugins/","text":"Template Plugins # 5.3+ anchor # The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink . 5.3+ anchorAttributes # anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default append # If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array. assign # New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } capture # In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture } concat # concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * } counter # counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default 5.4+ csrfToken # {csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details. currency # currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * } cycle # cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter date # date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date } 3.1+ dateInterval # dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set) encodeJS # encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script> encodeJSON # encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } ' escapeCDATA # escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]> event # event provides extension points in templates that template listeners can use. { event name = 'foo' } fetch # fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * } filesizeBinary # filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary } filesize # filesize formats the filesize using filesize (in bytes). { $filesize | filesize } hascontent # In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent } htmlCheckboxes # htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options htmlOptions # htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element. implode # implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration 5.2+ ipSearch # ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch } 3.0+ js # js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * } 5.3+ jslang # jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } ); lang # lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item. language # language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language } link # link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() . newlineToBreak # newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak } 3.0+ page # page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID ) pages # pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links plainTime # plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime } 5.3+ plural # plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer. prepend # If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array. shortUnit # shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * } smallpages # smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links. tableWordwrap # tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap } time # time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * } truncate # truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default 5.3+ user # user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"Template Plugins"},{"location":"view/template-plugins/#template-plugins","text":"","title":"Template Plugins"},{"location":"view/template-plugins/#53-anchor","text":"The anchor template plugin creates a HTML elements. The easiest way to use the template plugin is to pass it an instance of ITitledLinkObject : { anchor object = $object } generates the same output as <a href=\" { $object -> getLink () } \"> { $object -> getTitle () } </a> Instead of an object parameter, a link and content parameter can be used: { anchor link = $linkObject content = $content } where $linkObject implements ILinkableObject and $content is either an object implementing ITitledObject or having a __toString() method or $content is a string or a number. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , expect for href which is disallowed, are added as attributes to the anchor element. If an object attribute is present, the object also implements IPopoverObject and if the return value of IPopoverObject::getPopoverLinkClass() is included in the class attribute of the anchor tag, data-object-id is automatically added. This functionality makes it easy to generate links with popover support. Instead of <a href=\" { $entry -> getLink () } \" class=\"blogEntryLink\" data-object-id=\" { @ $entry -> entryID } \"> { $entry -> subject } </a> using { anchor object = $entry class = 'blogEntryLink' } is sufficient if Entry::getPopoverLinkClass() returns blogEntryLink .","title":"5.3+ anchor"},{"location":"view/template-plugins/#53-anchorattributes","text":"anchorAttributes compliments the StringUtil::getAnchorTagAttributes(string, bool): string method. It allows to easily generate the necessary attributes for an anchor tag based off the destination URL. <a href=\"https://www.example.com\" { anchorAttributes url = 'https://www.example.com' appendHref = false appendClassname = true isUgc = true } > Attribute Description url destination URL appendHref whether the href attribute should be generated; true by default isUgc whether the rel=\"ugc\" attribute should be generated; false by default appendClassname whether the class=\"externalURL\" attribute should be generated; true by default","title":"5.3+ anchorAttributes"},{"location":"view/template-plugins/#append","text":"If a string should be appended to the value of a variable, append can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { append var = templateVariable value = '2' } { $templateVariable } { * now prints 'newValue2 * } If the variables does not exist yet, append creates a new one with the given value. If append is used on an array as the variable, the value is appended to all elements of the array.","title":"append"},{"location":"view/template-plugins/#assign","text":"New template variables can be declared and new values can be assigned to existing template variables using assign : { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * }","title":"assign"},{"location":"view/template-plugins/#capture","text":"In some situations, assign is not sufficient to assign values to variables in templates if the value is complex. Instead, capture can be used: { capture var = templateVariable } { if $foo } <p> { $bar } </p> { else } <small> { $baz } </small> { /if } { /capture }","title":"capture"},{"location":"view/template-plugins/#concat","text":"concat is a modifier used to concatenate multiple strings: { assign var = foo value = 'foo' } { assign var = templateVariable value = 'bar' | concat : $foo } { $templateVariable } { * prints 'foobar * }","title":"concat"},{"location":"view/template-plugins/#counter","text":"counter can be used to generate and optionally print a counter: { counter name = fooCounter print = true } { * prints '1' * } { counter name = fooCounter print = true } { * prints '2' now * } { counter name = fooCounter } { * prints nothing, but counter value is '3' now internally * } { counter name = fooCounter print = true } { * prints '4' * } Counter supports the following attributes: Attribute Description assign optional name of the template variable the current counter value is assigned to direction counting direction, either up or down ; up by default name name of the counter, relevant if multiple counters are used simultaneously print if true , the current counter value is printed; false by default skip positive counting increment; 1 by default start start counter value; 1 by default","title":"counter"},{"location":"view/template-plugins/#54-csrftoken","text":"{csrfToken} prints out the session's CSRF token (\u201cSecurity Token\u201d). <form action=\" { link controller = \"Foo\" }{ /link } \" method=\"post\"> { * snip * } { csrfToken } </form> The {csrfToken} template plugin supports a type parameter. Specifying this parameter might be required in rare situations. Please check the implementation for details.","title":"5.4+ csrfToken"},{"location":"view/template-plugins/#currency","text":"currency is a modifier used to format currency values with two decimals using language dependent thousands separators and decimal point: { assign var = currencyValue value = 12.345 } { $currencyValue | currency } { * prints '12.34' * }","title":"currency"},{"location":"view/template-plugins/#cycle","text":"cycle can be used to cycle between different values: { cycle name = fooCycle values = 'bar,baz' } { * prints 'bar' * } { cycle name = fooCycle } { * prints 'baz' * } { cycle name = fooCycle advance = false } { * prints 'baz' again * } { cycle name = fooCycle } { * prints 'bar' * } The values attribute only has to be present for the first call. If cycle is used in a loop, the presence of the same values in consecutive calls has no effect. Only once the values change, the cycle is reset. Attribute Description advance if true , the current cycle value is advanced to the next value; true by default assign optional name of the template variable the current cycle value is assigned to; if used, print is set to false delimiter delimiter between the different cycle values; , by default name name of the cycle, relevant if multiple cycles are used simultaneously print if true , the current cycle value is printed, false by default reset if true , the current cycle value is set to the first value, false by default values string containing the different cycles values, also see delimiter","title":"cycle"},{"location":"view/template-plugins/#date","text":"date generated a formatted date using wcf\\util\\DateUtil::format() with DateUtil::DATE_FORMAT internally. { $timestamp | date }","title":"date"},{"location":"view/template-plugins/#31-dateinterval","text":"dateInterval calculates the difference between two unix timestamps and generated a textual date interval. { dateInterval start = $startTimestamp end = $endTimestamp full = true format = 'sentence' } Attribute Description end end of the time interval; current timestamp by default (though either start or end has to be set) format output format, either default , sentence , or plain ; defaults to default , see wcf\\util\\DateUtil::FORMAT_* constants full if true , full difference in minutes is shown; if false , only the longest time interval is shown; false by default start start of the time interval; current timestamp by default (though either start or end has to be set)","title":"3.1+ dateInterval"},{"location":"view/template-plugins/#encodejs","text":"encodeJS encodes a string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with \\' , linebreaks with \\n , and / with \\/ . <script> var foo = ' { @ $foo | encodeJS } '; </script>","title":"encodeJS"},{"location":"view/template-plugins/#encodejson","text":"encodeJSON encodes a JSON string to be used as a single-quoted string in JavaScript by replacing \\\\ with \\\\\\\\ , ' with &#39; , linebreaks with \\n , and / with \\/ . Additionally, htmlspecialchars is applied to the string. ' { @ $foo | encodeJSON } '","title":"encodeJSON"},{"location":"view/template-plugins/#escapecdata","text":"escapeCDATA encodes a string to be used in a CDATA element by replacing ]]> with ]]]]><![CDATA[> . <![CDATA[ { @ $foo | encodeCDATA } ]]>","title":"escapeCDATA"},{"location":"view/template-plugins/#event","text":"event provides extension points in templates that template listeners can use. { event name = 'foo' }","title":"event"},{"location":"view/template-plugins/#fetch","text":"fetch fetches the contents of a file using file_get_contents . { fetch file = 'foo.html' } { * prints the contents of `foo.html` * } { fetch file = 'bar.html' assign = bar } { * assigns the contents of `foo.html` to `$bar`; does not print the contents * }","title":"fetch"},{"location":"view/template-plugins/#filesizebinary","text":"filesizeBinary formats the filesize using binary filesize (in bytes). { $filesize | filesizeBinary }","title":"filesizeBinary"},{"location":"view/template-plugins/#filesize","text":"filesize formats the filesize using filesize (in bytes). { $filesize | filesize }","title":"filesize"},{"location":"view/template-plugins/#hascontent","text":"In many cases, conditional statements can be used to determine if a certain section of a template is shown: { if $foo === 'bar' } only shown if $foo is bar { /if } In some situations, however, such conditional statements are not sufficient. One prominent example is a template event: { if $foo === 'bar' } <ul> { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } </li> { /if } In this example, if $foo !== 'bar' , the list will not be shown, regardless of the additional template code provided by template listeners. In such a situation, hascontent has to be used: { hascontent } <ul> { content } { if $foo === 'bar' } <li>Bar</li> { /if } { event name = 'listItems' } { /content } </ul> { /hascontent } If the part of the template wrapped in the content tags has any (trimmed) content, the part of the template wrapped by hascontent tags is shown (including the part wrapped by the content tags), otherwise nothing is shown. Thus, this construct avoids an empty list compared to the if solution above. Like foreach , hascontent also supports an else part: { hascontent } <ul> { content } { * \u2026 * } { /content } </ul> { hascontentelse } no list { /hascontent }","title":"hascontent"},{"location":"view/template-plugins/#htmlcheckboxes","text":"htmlCheckboxes generates a list of HTML checkboxes. { htmlCheckboxes name = foo options = $fooOptions selected = $currentFoo } { htmlCheckboxes name = bar output = $barLabels values = $barValues selected = $currentBar } Attribute Description 5.2+ disabled if true , all checkboxes are disabled disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default name name attribute of the input checkbox element output array used as keys and values for options if present; not present by default options array selectable options with the key used as value attribute and the value as the checkbox label selected current selected value(s) separator separator between the different checkboxes in the generated output; empty string by default values array with values used in combination with output , where output is only used as keys for options","title":"htmlCheckboxes"},{"location":"view/template-plugins/#htmloptions","text":"htmlOptions generates an select HTML element. { htmlOptions name = 'foo' options = $options selected = $selected } <select name=\"bar\"> <option value=\"\" { if ! $selected } selected { /if } > { lang } foo.bar.default { /lang } </option> { htmlOptions options = $options selected = $selected } { * no `name` attribute * } </select> Attribute Description disableEncoding if true , the values are not passed through wcf\\util\\StringUtil::encodeHTML() ; false by default object optional instance of wcf\\data\\DatabaseObjectList that provides the selectable options (overwrites options attribute internally) name name attribute of the select element; if not present, only the contents of the select element are printed output array used as keys and values for options if present; not present by default values array with values used in combination with output , where output is only used as keys for options options array selectable options with the key used as value attribute and the value as the option label; if a value is an array, an optgroup is generated with the array key as the optgroup label selected current selected value(s) All additional attributes are added as attributes of the select HTML element.","title":"htmlOptions"},{"location":"view/template-plugins/#implode","text":"implodes transforms an array into a string and prints it. { implode from = $array key = key item = item glue = \";\" }{ $key } : { $value }{ /implode } Attribute Description from array with the imploded values glue separator between the different array values; ', ' by default item template variable name where the current array value is stored during the iteration key optional template variable name where the current array key is stored during the iteration","title":"implode"},{"location":"view/template-plugins/#52-ipsearch","text":"ipSearch generates a link to search for an IP address. { \"127.0.0.1\" | ipSearch }","title":"5.2+ ipSearch"},{"location":"view/template-plugins/#30-js","text":"js generates script tags based on whether ENABLE_DEBUG_MODE and VISITOR_USE_TINY_BUILD are enabled. { js application = 'wbb' file = 'WBB' } { * generates 'http://example.com/js/WBB.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' if ENABLE_DEBUG_MODE=0 * } { js application = 'wcf' lib = 'jquery' } { * generates 'http://example.com/wcf/js/3rdParty/jquery.js' * } { js application = 'wcf' lib = 'jquery-ui' file = 'awesomeWidget' } { * generates 'http://example.com/wcf/js/3rdParty/jquery-ui/awesomeWidget.js' * } { js application = 'wcf' file = 'WCF.Like' bundle = 'WCF.Combined' hasTiny = true } { * generates 'http://example.com/wcf/js/WCF.Like.js' if ENABLE_DEBUG_MODE=1 * } { * generates 'http://example.com/wcf/js/WCF.Combined.min.js' (ENABLE_DEBUG_MODE=0 * } { * generates 'http://example.com/wcf/js/WCF.Combined.tiny.min.js' if ENABLE_DEBUG_MODE=0 and VISITOR_USE_TINY_BUILD=1 * }","title":"3.0+ js"},{"location":"view/template-plugins/#53-jslang","text":"jslang works like lang with the difference that the resulting string is automatically passed through encodeJS . require(['Language', /* \u2026 */], function(Language, /* \u2026 */) { Language . addObject ( { 'app.foo.bar' : '{jslang}app.foo.bar{/jslang}' , } ); // \u2026 } );","title":"5.3+ jslang"},{"location":"view/template-plugins/#lang","text":"lang replaces a language items with its value. { lang } foo.bar.baz { /lang } { lang __literal = true } foo.bar.baz { /lang } { lang foo = 'baz' } foo.bar.baz { /lang } { lang } foo.bar.baz. { $action }{ /lang } Attribute Description __encode if true , the output will be passed through StringUtil::encodeHTML() __literal if true , template variables will not resolved but printed as they are in the language item; false by default __optional if true and the language item does not exist, an empty string is printed; false by default All additional attributes are available when parsing the language item.","title":"lang"},{"location":"view/template-plugins/#language","text":"language replaces a language items with its value. If the template variable __language exists, this language object will be used instead of WCF::getLanguage() . This modifier is useful when assigning the value directly to a variable. { $languageItem | language } { assign var = foo value = $languageItem | language }","title":"language"},{"location":"view/template-plugins/#link","text":"link generates internal links using LinkHandler . <a href=\" { link controller = 'FooList' application = 'bar' } param1=2&param2=A { /link } \">Foo</a> Attribute Description application abbreviation of the application the controller belongs to; wcf by default controller name of the controller; if not present, the landing page is linked in the frontend and the index page in the ACP encode if true , the generated link is passed through wcf\\util\\StringUtil::encodeHTML() ; true by default isEmail sets encode=false and forces links to link to the frontend Additional attributes are passed to LinkHandler::getLink() .","title":"link"},{"location":"view/template-plugins/#newlinetobreak","text":"newlineToBreak transforms newlines into HTML <br> elements after encoding the content via wcf\\util\\StringUtil::encodeHTML() . { $foo | newlineToBreak }","title":"newlineToBreak"},{"location":"view/template-plugins/#30-page","text":"page generates an internal link to a CMS page. { page } com.woltlab.wcf.CookiePolicy { /page } { page pageID = 1 }{ /page } { page language = 'de' } com.woltlab.wcf.CookiePolicy { /page } { page languageID = 2 } com.woltlab.wcf.CookiePolicy { /page } Attribute Description pageID unique id of the page (cannot be used together with a page identifier as value) languageID id of the page language (cannot be used together with language ) language language code of the page language (cannot be used together with languageID )","title":"3.0+ page"},{"location":"view/template-plugins/#pages","text":"pages generates a pagination. { pages controller = 'FooList' link = \"pageNo=%d\" print = true assign = pagesLinks } { * prints pagination * } { @ $pagesLinks } { * prints same pagination again * } Attribute Description assign optional name of the template variable the pagination is assigned to controller controller name of the generated links link additional link parameter where %d will be replaced with the relevant page number pages maximum number of of pages; by default, the template variable $pages is used print if false and assign=true , the pagination is not printed application , id , object , title additional parameters passed to LinkHandler::getLink() to generate page links","title":"pages"},{"location":"view/template-plugins/#plaintime","text":"plainTime formats a timestamp to include year, month, day, hour, and minutes. The exact formatting depends on the current language (via the language items wcf.date.dateTimeFormat , wcf.date.dateFormat , and wcf.date.timeFormat ). { $timestamp | plainTime }","title":"plainTime"},{"location":"view/template-plugins/#53-plural","text":"plural allows to easily select the correct plural form of a phrase based on a given value . The pluralization logic follows the Unicode Language Plural Rules for cardinal numbers. The # placeholder within the resulting phrase is replaced by the value . It is automatically formatted using StringUtil::formatNumeric . English: Note the use of 1 if the number ( # ) is not used within the phrase and the use of one otherwise. They are equivalent for English, but following this rule generalizes better to other languages, helping the translator. { assign var = numberOfWorlds value = 2 } <h1>Hello { plural value = $numberOfWorlds 1 = 'World' other = 'Worlds' } !</h1> <p>There { plural value = $numberOfWorlds 1 = 'is one world' other = 'are # worlds' } !</p> <p>There { plural value = $numberOfWorlds one = 'is # world' other = 'are # worlds' } !</p> German: { assign var = numberOfWorlds value = 2 } <h1>Hallo { plural value = $numberOfWorlds 1 = 'Welt' other = 'Welten' } !</h1> <p>Es gibt { plural value = $numberOfWorlds 1 = 'eine Welt' other = '# Welten' } !</p> <p>Es gibt { plural value = $numberOfWorlds one = '# Welt' other = '# Welten' } !</p> Romanian: Note the additional use of few which is not required in English or German. { assign var = numberOfWorlds value = 2 } <h1>Salut { plural value = $numberOfWorlds 1 = 'lume' other = 'lumi' } !</h1> <p>Exist\u0103 { plural value = $numberOfWorlds 1 = 'o lume' few = '# lumi' other = '# de lumi' } !</p> <p>Exist\u0103 { plural value = $numberOfWorlds one = '# lume' few = '# lumi' other = '# de lumi' } !</p> Russian: Note the difference between 1 (exactly 1 ) and one (ending in 1 , except ending in 11 ). { assign var = numberOfWorlds value = 2 } <h1>\u041f\u0440\u0438\u0432\u0435\u0442 { plural value = $numberOfWorld 1 = '\u043c\u0438\u0440' other = '\u043c\u0438\u0440\u044b' } !</h1> <p>\u0415\u0441\u0442\u044c { plural value = $numberOfWorlds 1 = '\u043c\u0438\u0440' one = '# \u043c\u0438\u0440' few = '# \u043c\u0438\u0440\u0430' many = '# \u043c\u0438\u0440\u043e\u0432' other = '# \u043c\u0438\u0440\u043e\u0432' } !</p> Attribute Description value The value that is used to select the proper phrase. other The phrase that is used when no other selector matches. Any Category Name The phrase that is used when value belongs to the named category. Available categories depend on the language. Any Integer The phrase that is used when value is that exact integer.","title":"5.3+ plural"},{"location":"view/template-plugins/#prepend","text":"If a string should be prepended to the value of a variable, prepend can be used: { assign var = templateVariable value = 'newValue' } { $templateVariable } { * prints 'newValue * } { prepend var = templateVariable value = '2' } { $templateVariable } { * now prints '2newValue' * } If the variables does not exist yet, prepend creates a new one with the given value. If prepend is used on an array as the variable, the value is prepended to all elements of the array.","title":"prepend"},{"location":"view/template-plugins/#shortunit","text":"shortUnit shortens numbers larger than 1000 by using unit suffixes: { 10000 | shortUnit } { * prints 10k * } { 5400000 | shortUnit } { * prints 5.4M * }","title":"shortUnit"},{"location":"view/template-plugins/#smallpages","text":"smallpages generates a smaller version of pages by using adding the small CSS class to the generated <nav> element and only showing 7 instead of 9 links.","title":"smallpages"},{"location":"view/template-plugins/#tablewordwrap","text":"tableWordwrap inserts zero width spaces every 30 characters in words longer than 30 characters. { $foo | tableWordwrap }","title":"tableWordwrap"},{"location":"view/template-plugins/#time","text":"time generates an HTML time elements based on a timestamp that shows a relative time or the absolute time if the timestamp more than six days ago. { $timestamp | time } { * prints a '<time>' element * }","title":"time"},{"location":"view/template-plugins/#truncate","text":"truncate truncates a long string into a shorter one: { $foo | truncate : 35 } { $foo | truncate : 35 : '_' : true } Parameter Number Description 0 truncated string 1 truncated length; 80 by default 2 ellipsis symbol; wcf\\util\\StringUtil::HELLIP by default 3 if true , words can be broken up in the middle; false by default","title":"truncate"},{"location":"view/template-plugins/#53-user","text":"user generates links to user profiles. The mandatory object parameter requires an instances of UserProfile . The optional type parameter is responsible for what the generated link contains: type='default' (also applies if no type is given) outputs the formatted username relying on the \u201cUser Marking\u201d setting of the relevant user group. Additionally, the user popover card will be shown when hovering over the generated link. type='plain' outputs the username without additional formatting. type='avatar(\\d+)' outputs the user\u2019s avatar in the specified size, i.e., avatar48 outputs the avatar with a width and height of 48 pixels. The last special attribute is append whose contents are appended to the href attribute of the generated anchor element. All of the other attributes matching ~^[a-z]+([A-z]+)+$~ , except for href which may not be added, are added as attributes to the anchor element. Examples: { user object = $user } generates <a href=\" { $user -> getLink () } \" data-object-id=\" { $user -> userID } \" class=\"userLink\"> { @ $user -> getFormattedUsername () } </a> and { user object = $user type = 'avatar48' foo = 'bar' } generates <a href=\" { $user -> getLink () } \" foo=\"bar\"> { @ $object -> getAvatar ()-> getImageTag ( 48 ) } </a>","title":"5.3+ user"},{"location":"view/templates/","text":"Templates # Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore. Template Types and Conventions # WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal. Installing Templates # Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages. Base Templates # Frontend # { include file = 'header' } { * content * } { include file = 'footer' } Backend # { include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page. Common Template Components # Forms # For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form> Tab Menus # <div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div> Template Scripting # Template Variables # Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]); Modifiers # If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed. System Template Variable # The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend). Comments # Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template. Conditions # Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if } Foreach Loops # Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach } Including Other Templates # To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' } Template Plugins # An overview of all available template plugins can be found here .","title":"Templates"},{"location":"view/templates/#templates","text":"Templates are responsible for the output a user sees when requesting a page (while the PHP code is responsible for providing the data that will be shown). Templates are text files with .tpl as the file extension. WoltLab Suite Core compiles the template files once into a PHP file that is executed when a user requests the page. In subsequent request, as the PHP file containing the compiled template already exists, compiling the template is not necessary anymore.","title":"Templates"},{"location":"view/templates/#template-types-and-conventions","text":"WoltLab Suite Core supports two types of templates: frontend templates (or simply templates ) and backend templates ( ACP templates ). Each type of template is only available in its respective domain, thus frontend templates cannot be included or used in the ACP and vice versa. For pages and forms, the name of the template matches the unqualified name of the PHP class except for the Page or Form suffix: RegisterForm.class.php \u2192 register.tpl UserPage.class.php \u2192 user.tpl If you follow this convention, WoltLab Suite Core will automatically determine the template name so that you do not have to explicitly set it. For forms that handle creating and editing objects, in general, there are two form classes: FooAddForm and FooEditForm . WoltLab Suite Core, however, generally only uses one template fooAdd.tpl and the template variable $action to distinguish between creating a new object ( $action = 'add' ) and editing an existing object ( $action = 'edit' ) as the differences between templates for adding and editing an object are minimal.","title":"Template Types and Conventions"},{"location":"view/templates/#installing-templates","text":"Templates and ACP templates are installed by two different package installation plugins: the template PIP and the ACP template PIP . More information about installing templates can be found on those pages.","title":"Installing Templates"},{"location":"view/templates/#base-templates","text":"","title":"Base Templates"},{"location":"view/templates/#frontend","text":"{ include file = 'header' } { * content * } { include file = 'footer' }","title":"Frontend"},{"location":"view/templates/#backend","text":"{ include file = 'header' pageTitle = 'foo.bar.baz' } <header class=\"contentHeader\"> <div class=\"contentHeaderTitle\"> <h1 class=\"contentTitle\">Title</h1> </div> <nav class=\"contentHeaderNavigation\"> <ul> { * your default content header navigation buttons * } { event name = 'contentHeaderNavigation' } </ul> </nav> </header> { * content * } { include file = 'footer' } foo.bar.baz is the language item that contains the title of the page.","title":"Backend"},{"location":"view/templates/#common-template-components","text":"","title":"Common Template Components"},{"location":"view/templates/#forms","text":"For new forms, use the new form builder API introduced with WoltLab Suite 5.2. <form method=\"post\" action=\" { link controller = 'FooBar' }{ /link } \"> <div class=\"section\"> <dl { if $errorField == 'baz' } class=\"formError\" { /if } > <dt><label for=\"baz\"> { lang } foo.bar.baz { /lang } </label></dt> <dd> <input type=\"text\" id=\"baz\" name=\"baz\" value=\" { $baz } \" class=\"long\" required autofocus> { if $errorField == 'baz' } <small class=\"innerError\"> { if $errorType == 'empty' } { lang } wcf.global.form.error.empty { /lang } { else } { lang } foo.bar.baz.error. { @ $errorType }{ /lang } { /if } </small> { /if } </dd> </dl> <dl> <dt><label for=\"bar\"> { lang } foo.bar.bar { /lang } </label></dt> <dd> <textarea name=\"bar\" id=\"bar\" cols=\"40\" rows=\"10\"> { $bar } </textarea> { if $errorField == 'bar' } <small class=\"innerError\"> { lang } foo.bar.bar.error. { @ $errorType }{ /lang } </small> { /if } </dd> </dl> { * other fields * } { event name = 'dataFields' } </div> { * other sections * } { event name = 'sections' } <div class=\"formSubmit\"> <input type=\"submit\" value=\" { lang } wcf.global.button.submit { /lang } \" accesskey=\"s\"> { csrfToken } </div> </form>","title":"Forms"},{"location":"view/templates/#tab-menus","text":"<div class=\"section tabMenuContainer\"> <nav class=\"tabMenu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab1' ) } \">Tab 1</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2' ) } \">Tab 2</a></li> { event name = 'tabMenuTabs' } </ul> </nav> <div id=\"tab1\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first tab * } </div> </div> <div id=\"tab2\" class=\"tabMenuContainer tabMenuContent\"> <nav class=\"menu\"> <ul> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2A' ) } \">Tab 2A</a></li> <li><a href=\" { @ $__wcf -> getAnchor ( 'tab2B' ) } \">Tab 2B</a></li> { event name = 'tabMenuTab2Subtabs' } </ul> </nav> <div id=\"tab2A\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of first subtab for second tab * } </div> </div> <div id=\"tab2B\" class=\"tabMenuContent\"> <div class=\"section\"> { * contents of second subtab for second tab * } </div> </div> { event name = 'tabMenuTab2Contents' } </div> { event name = 'tabMenuContents' } </div>","title":"Tab Menus"},{"location":"view/templates/#template-scripting","text":"","title":"Template Scripting"},{"location":"view/templates/#template-variables","text":"Template variables can be assigned via WCF::getTPL()->assign('foo', 'bar') and accessed in templates via $foo : {$foo} will result in the contents of $foo to be passed to StringUtil::encodeHTML() before being printed. {#$foo} will result in the contents of $foo to be passed to StringUtil::formatNumeric() before being printed. Thus, this method is relevant when printing numbers and having them formatted correctly according the the user\u2019s language. {@$foo} will result in the contents of $foo to be printed directly. In general, this method should not be used for user-generated input. Multiple template variables can be assigned by passing an array: WCF :: getTPL () -> assign ([ 'foo' => 'bar' , 'baz' => false ]);","title":"Template Variables"},{"location":"view/templates/#modifiers","text":"If you want to call a function on a variable, you can use the modifier syntax: {@$foo|trim} , for example, results in the trimmed contents of $foo to be printed.","title":"Modifiers"},{"location":"view/templates/#system-template-variable","text":"The template variable $tpl is automatically assigned and is an array containing different data: $tpl[get] contains $_GET . $tpl[post] contains $_POST . $tpl[cookie] contains $_COOKIE . $tpl[server] contains $_SERVER . $tpl[env] contains $_ENV . $tpl[now] contains TIME_NOW (current timestamp). Furthermore, the following template variables are also automatically assigned: $__wcf contains the WCF object (or WCFACP object in the backend).","title":"System Template Variable"},{"location":"view/templates/#comments","text":"Comments are wrapped in {* and *} and can span multiple lines: { * some comment * } The template compiler discards the comments, so that they not included in the compiled template.","title":"Comments"},{"location":"view/templates/#conditions","text":"Conditions follow a similar syntax to PHP code: { if $foo === 'bar' } foo is bar { elseif $foo === 'baz' } foo is baz { else } foo is neither bar nor baz { /if } The supported operators in conditions are === , !== , == , != , <= , < , >= , > , || , && , ! , and = . More examples: { if $bar | isset } \u2026 { /if } { if $bar | count > 3 && $bar | count < 100 } \u2026 { /if }","title":"Conditions"},{"location":"view/templates/#foreach-loops","text":"Foreach loops allow to iterate over arrays or iterable objects: <ul> { foreach from = $array key = key item = value } <li> { $key } : { $value } </li> { /foreach } </ul> While the from attribute containing the iterated structure and the item attribute containg the current value are mandatory, the key attribute is optional. If the foreach loop has a name assigned to it via the name attribute, the $tpl template variable provides additional data about the loop: <ul> { foreach from = $array key = key item = value name = foo } { if $tpl [ foreach ][ foo ][ first ] } something special for the first iteration { elseif $tpl [ foreach ][ foo ][ last ] } something special for the last iteration { /if } <li>iteration { # $tpl [ foreach ][ foo ][ iteration ]+ 1 } out of { # $tpl [ foreach ][ foo ][ total ] } { $key } : { $value } </li> { /foreach } </ul> In contrast to PHP\u2019s foreach loop, templates also support foreachelse : { foreach from = $array item = value } \u2026 { foreachelse } there is nothing to iterate over { /foreach }","title":"Foreach Loops"},{"location":"view/templates/#including-other-templates","text":"To include template named foo from the same domain (frontend/backend), you can use { include file = 'foo' } If the template belongs to an application, you have to specify that application using the application attribute: { include file = 'foo' application = 'app' } Additional template variables can be passed to the included template as additional attributes: { include file = 'foo' application = 'app' var1 = 'foo1' var2 = 'foo2' }","title":"Including Other Templates"},{"location":"view/templates/#template-plugins","text":"An overview of all available template plugins can be found here .","title":"Template Plugins"}]}
\ No newline at end of file
index 3da30ec63c1d9d5119276da721a14d92460a0457..137f930e4e071735d5871498eef22dd94052d238 100644 (file)
 <?xml version="1.0" encoding="UTF-8"?>
 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url><url>
      <loc>None</loc>
-     <lastmod>2021-03-24</lastmod>
+     <lastmod>2021-04-06</lastmod>
      <changefreq>daily</changefreq>
     </url>
 </urlset>
\ No newline at end of file
index 2551044bd4175a2f29bf0f3c9d075563940cee3a..99bf4b32837859a153626dac90e3ab2b6b6d2266 100644 (file)
Binary files a/latest/sitemap.xml.gz and b/latest/sitemap.xml.gz differ
index bfd738f9164bf50ce3642343e438ec4eab96ed0d..b3235871ce39a698ff9d298f947751edb008d6b9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1846,6 +1842,7 @@ Note that in the context of this example, not every added feature might make per
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -1910,10 +1907,10 @@ Note that in the context of this example, not every added feature might make per
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 0da55947ad3362095e68f10e895ea7d0e2332789..e5d5abbc534e8cc8552f8d497f22d2d6589c9d3c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -3347,6 +3343,7 @@ As the menu item package installation plugin validates the given page and throws
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -3411,10 +3408,10 @@ As the menu item package installation plugin validates the given page and throws
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 1c6652240b1888aa03005ee732f57797c3252c40..7587d728a178556843bc5073a307dd1d2806f8f9 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2405,6 +2401,7 @@ In contrast, reading the existing birthday from a person is only relevant for ed
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2469,10 +2466,10 @@ In contrast, reading the existing birthday from a person is only relevant for ed
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index fe052c71d89bd2d158752073c19890532de9f837..b7c8e84f71f11c8dd8fed2c34428d2d89cba384f 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2528,6 +2524,7 @@ The <code>IOnlineLocationPageHandler</code> interface requires two methods to be
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2578,10 +2575,10 @@ The <code>IOnlineLocationPageHandler</code> interface requires two methods to be
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 300012ec4eb9929e422fe141fe2aefc8ab60eac9..547716156e087537e6ead4e24ae4ab875b5ac11e 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2105,6 +2101,7 @@ To communicate the preloading intent to the compiler, the <code>--woltlab-suite-
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2169,10 +2166,10 @@ To communicate the preloading intent to the compiler, the <code>--woltlab-suite-
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index 02673b8df968a3540b6565e084cc94dc838cf77d..5db76b5b0f2349e1c203dfa1a962d2ec6417f61c 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2125,6 +2121,7 @@ There are several general error messages like <code>wcf.global.form.error.empty<
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2156,10 +2153,10 @@ There are several general error messages like <code>wcf.global.form.error.empty<
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index de65a9710ebbd9d9131acc25ce9e1a5272f53067..1738b40d77d640a126d9cc4d9a405b062c660ca7 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -1977,6 +1973,7 @@ direction.</p>
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2041,10 +2038,10 @@ direction.</p>
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index b2e4ca9a31c85368d3e22c7b9fe5bfe2e8f765ff..4247b1fe7c2012917cbe0c1a15ebf7d601616b96 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2888,6 +2884,7 @@ The optional <code>type</code> parameter is responsible for what the generated l
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2919,10 +2916,10 @@ The optional <code>type</code> parameter is responsible for what the generated l
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>
index de3bb72d6089fb523ccb15c79259222b338c6f25..ea0e906ce64bbab44030399e4a8bb2987fa08123 100644 (file)
@@ -9,8 +9,8 @@
       
       
       
-      <link rel="shortcut icon" href="../../assets/default.favicon.ico">
-      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.0.5">
+      <link rel="icon" href="../../assets/default.favicon.ico">
+      <meta name="generator" content="mkdocs-1.1.2, mkdocs-material-7.1.0">
     
     
       
       
     
     
-      <link rel="stylesheet" href="../../assets/stylesheets/main.77f3fd56.min.css">
+      <link rel="stylesheet" href="../../assets/stylesheets/main.33e2939f.min.css">
       
         
-        <link rel="stylesheet" href="../../assets/stylesheets/palette.7fa14f5b.min.css">
+        <link rel="stylesheet" href="../../assets/stylesheets/palette.ef6f36e2.min.css">
         
           
           
     
     
     <body dir="ltr" data-md-color-scheme="" data-md-color-primary="teal" data-md-color-accent="">
-      
   
     
+    <script>function __prefix(e){return new URL("../..",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
+    
     <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
     <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
     <label class="md-overlay" for="__drawer"></label>
       
     </div>
     
-      
-
-<header class="md-header" data-md-component="header">
+      <header class="md-header" data-md-component="header">
   <nav class="md-header__inner md-grid" aria-label="Header">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-header__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
           </span>
         </div>
       </div>
-    </div>
-    <div class="md-header__options">
-      
     </div>
     
+    
+    
       <label class="md-header__button md-icon" for="__search">
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
       </label>
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
       </label>
       <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
       </button>
     </form>
     <div class="md-search__output">
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
                     
 
 
-
-
 <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
   <label class="md-nav__title" for="__drawer">
-    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation">
+    <a href="../.." title="WoltLab Suite Documentation" class="md-nav__button md-logo" aria-label="WoltLab Suite Documentation" data-md-component="logo">
       
   <img src="../../assets/logo.png" alt="logo">
 
 <a href="https://github.com/WoltLab/docs.woltlab.com/" title="Go to repository" class="md-source" data-md-component="source">
   <div class="md-source__icon md-icon">
     
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
   </div>
   <div class="md-source__repository">
     GitHub
@@ -2393,6 +2389,7 @@ If the foreach loop has a name assigned to it via the <code>name</code> attribut
             </article>
           </div>
         </div>
+        
       </main>
       
         
@@ -2457,10 +2454,10 @@ If the foreach loop has a name assigned to it via the <code>name</code> attribut
     <div class="md-dialog" data-md-component="dialog">
       <div class="md-dialog__inner md-typeset"></div>
     </div>
-    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fb4a9340.min.js", "version": {"provider": "mike"}}</script>
+    <script id="__config" type="application/json">{"base": "../..", "features": [], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}, "search": "../../assets/javascripts/workers/search.fe42c31b.min.js", "version": {"provider": "mike"}}</script>
     
     
-      <script src="../../assets/javascripts/bundle.5cf3e710.min.js"></script>
+      <script src="../../assets/javascripts/bundle.d892486b.min.js"></script>
       
     
   </body>